diff --git a/.tool/lint b/.tool/lint index f25a668b9..e7c9bab87 100755 --- a/.tool/lint +++ b/.tool/lint @@ -17,7 +17,7 @@ for d in $(find . -type d -not -iwholename '*.git*' -a -not -iname '.tool' -a -n --exclude='schema/fs.go' \ --disable=aligncheck \ --disable=gotype \ - --cyclo-over=20 \ + --cyclo-over=35 \ --tests \ --deadline=10s "${d}" done diff --git a/cmd/oci-image-tool/create_runtime_bundle.go b/cmd/oci-image-tool/create_runtime_bundle.go index 1ec8ab167..57b6745bf 100644 --- a/cmd/oci-image-tool/create_runtime_bundle.go +++ b/cmd/oci-image-tool/create_runtime_bundle.go @@ -82,6 +82,11 @@ func (v *bundleCmd) Run(cmd *cobra.Command, args []string) { os.Exit(1) } + if _, err := os.Stat(args[1]); os.IsNotExist(err) { + v.stderr.Printf("destination path %s does not exist", args[1]) + os.Exit(1) + } + if v.typ == "" { typ, err := autodetect(args[0]) if err != nil { diff --git a/image/config.go b/image/config.go index d486c01eb..994d2ffd7 100644 --- a/image/config.go +++ b/image/config.go @@ -51,7 +51,7 @@ type config struct { func findConfig(w walker, d *descriptor) (*config, error) { var c config - cpath := filepath.Join("blobs", d.Digest) + cpath := filepath.Join("blobs", d.normalizeDigest()) f := func(path string, info os.FileInfo, r io.Reader) error { if info.IsDir() { @@ -97,12 +97,21 @@ func (c *config) runtimeSpec(rootfs string) (*specs.Spec, error) { var s specs.Spec s.Version = "0.5.0" + // we should at least apply the default spec, otherwise this is totally useless + s.Process.Terminal = true s.Root.Path = rootfs - s.Process.Cwd = c.Config.WorkingDir - s.Process.Env = append([]string(nil), c.Config.Env...) - s.Process.Args = append([]string(nil), c.Config.Entrypoint...) + s.Process.Cwd = "/" + if c.Config.WorkingDir != "" { + s.Process.Cwd = c.Config.WorkingDir + } + s.Process.Env = append(s.Process.Env, c.Config.Env...) + s.Process.Args = append(s.Process.Args, c.Config.Entrypoint...) s.Process.Args = append(s.Process.Args, c.Config.Cmd...) + if len(s.Process.Args) == 0 { + s.Process.Args = append(s.Process.Args, "sh") + } + if uid, err := strconv.Atoi(c.Config.User); err == nil { s.Process.User.UID = uint32(uid) } else if ug := strings.Split(c.Config.User, ":"); len(ug) == 2 { @@ -118,7 +127,7 @@ func (c *config) runtimeSpec(rootfs string) (*specs.Spec, error) { s.Process.User.UID = uint32(uid) s.Process.User.GID = uint32(gid) - } else { + } else if c.Config.User != "" { return nil, errors.New("config.User: unsupported format") } diff --git a/image/descriptor.go b/image/descriptor.go index ca576e617..9a5d183ee 100644 --- a/image/descriptor.go +++ b/image/descriptor.go @@ -22,6 +22,7 @@ import ( "io" "os" "path/filepath" + "strings" "github.com/pkg/errors" ) @@ -32,6 +33,10 @@ type descriptor struct { Size int64 `json:"size"` } +func (d *descriptor) normalizeDigest() string { + return strings.Replace(d.Digest, ":", "-", -1) +} + func findDescriptor(w walker, name string) (*descriptor, error) { var d descriptor dpath := filepath.Join("refs", name) @@ -71,7 +76,7 @@ func (d *descriptor) validate(w walker) error { } digest, err := filepath.Rel("blobs", filepath.Clean(path)) - if err != nil || d.Digest != digest { + if err != nil || d.normalizeDigest() != digest { return nil // ignore } @@ -84,11 +89,11 @@ func (d *descriptor) validate(w walker) error { switch err := w.walk(f); err { case nil: - return fmt.Errorf("%s: not found", d.Digest) + return fmt.Errorf("%s: not found", d.normalizeDigest()) case errEOW: // found, continue below default: - return errors.Wrapf(err, "%s: validation failed", d.Digest) + return errors.Wrapf(err, "%s: validation failed", d.normalizeDigest()) } return nil diff --git a/image/manifest.go b/image/manifest.go index 61e76747e..74cbfebca 100644 --- a/image/manifest.go +++ b/image/manifest.go @@ -38,7 +38,7 @@ type manifest struct { func findManifest(w walker, d *descriptor) (*manifest, error) { var m manifest - mpath := filepath.Join("blobs", d.Digest) + mpath := filepath.Join("blobs", d.normalizeDigest()) f := func(path string, info os.FileInfo, r io.Reader) error { if info.IsDir() { @@ -107,7 +107,7 @@ func (m *manifest) unpack(w walker, dest string) error { } dd, err := filepath.Rel("blobs", filepath.Clean(path)) - if err != nil || d.Digest != dd { + if err != nil || d.normalizeDigest() != dd { return nil // ignore } @@ -134,6 +134,7 @@ func unpackLayer(dest string, r io.Reader) error { } defer gz.Close() + var dirs []*tar.Header tr := tar.NewReader(gz) loop: @@ -148,8 +149,26 @@ loop: return errors.Wrapf(err, "error advancing tar stream") } - path := filepath.Join(dest, filepath.Clean(hdr.Name)) + hdr.Name = filepath.Clean(hdr.Name) + if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) { + // Not the root directory, ensure that the parent directory exists + parent := filepath.Dir(hdr.Name) + parentPath := filepath.Join(dest, parent) + if _, err2 := os.Lstat(parentPath); err2 != nil && os.IsNotExist(err2) { + if err3 := os.MkdirAll(parentPath, 0777); err3 != nil { + return err3 + } + } + } + path := filepath.Join(dest, hdr.Name) + rel, err := filepath.Rel(dest, path) + if err != nil { + return err + } info := hdr.FileInfo() + if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { + return fmt.Errorf("%q is outside of %q", hdr.Name, dest) + } if strings.HasPrefix(info.Name(), ".wh.") { path = strings.Replace(path, ".wh.", "", 1) @@ -163,12 +182,14 @@ loop: switch hdr.Typeflag { case tar.TypeDir: - if err := os.MkdirAll(path, info.Mode()); err != nil { - return errors.Wrap(err, "error creating directory") + if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) { + if err2 := os.MkdirAll(path, info.Mode()); err2 != nil { + return errors.Wrap(err2, "error creating directory") + } } case tar.TypeReg, tar.TypeRegA: - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode()) + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, info.Mode()) if err != nil { return errors.Wrap(err, "unable to open file") } @@ -200,13 +221,24 @@ loop: if err := os.Symlink(hdr.Linkname, path); err != nil { return err } - + case tar.TypeXGlobalHeader: + return nil + } + // Directory mtimes must be handled at the end to avoid further + // file creation in them to modify the directory mtime + if hdr.Typeflag == tar.TypeDir { + dirs = append(dirs, hdr) } + } + for _, hdr := range dirs { + path := filepath.Join(dest, hdr.Name) - if err := os.Chtimes(path, time.Now().UTC(), info.ModTime()); err != nil { + finfo := hdr.FileInfo() + // I believe the old version was using time.Now().UTC() to overcome an + // invalid error from chtimes.....but here we lose hdr.AccessTime like this... + if err := os.Chtimes(path, time.Now().UTC(), finfo.ModTime()); err != nil { return errors.Wrap(err, "error changing time") } } - return nil }