Skip to content

Commit

Permalink
Ensure files and links have missing parent dirs created. Support hard…
Browse files Browse the repository at this point in the history
… links

Signed-off-by: Jose R. Gonzalez <komish@flutes.dev>
  • Loading branch information
komish committed Jun 27, 2024
1 parent b926353 commit d4dbf03
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 2 deletions.
66 changes: 64 additions & 2 deletions internal/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,13 @@ func untar(ctx context.Context, dst string, r io.Reader) error {

// if it's a file create it
case tar.TypeReg:
// If the file's parent dir doesn't exist, create it.
dirname := filepath.Dir(target)
if _, err := os.Stat(dirname); err != nil {
if err := os.MkdirAll(dirname, 0o755); err != nil {
return err
}
}
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return err
Expand All @@ -429,15 +436,70 @@ func untar(ctx context.Context, dst string, r io.Reader) error {

// if it's a link create it
case tar.TypeSymlink:
err := os.Symlink(header.Linkname, filepath.Join(dst, header.Name))
nobaseLinkname, nobaseName := resolveLinkPaths(header.Linkname, header.Name)
fullLinkname := filepath.Join(dst, nobaseLinkname)
fullName := filepath.Join(dst, nobaseName)
// Safeguard for cases where we're trying to link to something
// outside of our base fs.
if !strings.HasPrefix(fullLinkname, dst) {
logger.V(log.DBG).Info("Error processing symlink. Symlink would reach outside of the image archive. Skipping this link", "link", header.Name, "linkedTo", header.Linkname, "resolvedTo", fullLinkname)
continue
}
// Create the new link's directory if it doesn't exist.
dirname := filepath.Dir(fullName)
if _, err := os.Stat(dirname); err != nil {
if err := os.MkdirAll(dirname, 0o755); err != nil {
return err
}
}
err := os.Symlink(fullLinkname, fullName)
if err != nil {
logger.V(log.DBG).Info(fmt.Sprintf("Error creating symlink: %s. Ignoring.", header.Name), "link", fullName, "linkedTo", fullLinkname, "reason", err)
continue
}
case tar.TypeLink:
// We assume hard links will not contain relative pathing in the archive.
original := filepath.Join(dst, header.Linkname)
// Create the new link's directory if it doesn't exist.
if !strings.HasPrefix(original, dst) {
logger.V(log.DBG).Info("Error processing symlink. Symlink would reach outside of the image archive. Skipping this link", "link", header.Name, "linkedTo", header.Linkname, "resolvedTo", original)
continue
}
dirname := filepath.Dir(target)
if _, err := os.Stat(dirname); err != nil {
if err := os.MkdirAll(dirname, 0o755); err != nil {
return err
}
}
err := os.Link(original, target)
if err != nil {
logger.V(log.DBG).Info(fmt.Sprintf("Error creating link: %s. Ignoring.", header.Name))
logger.V(log.DBG).Info(fmt.Sprintf("Error creating hard link: %s. Ignoring.", header.Name), "link", target, "linkedTo", original, "reason", err)
continue
}
}
}
}

// resolveLinkPaths determines if oldname is an absolute path or a relative
// path, and returns oldname relative to newname if necessary.
func resolveLinkPaths(oldname, newname string) (string, string) {
if filepath.IsAbs(oldname) {
return oldname, newname
}

linkDir := filepath.Dir(newname)
// If the newname is at the root of the filesystem, but the oldname is
// relative, we'll swap out the value we get from filepath.Dir for a / to
// allow relative pathing to resolve. This strips `..` references given the
// link exists at the very base of the filesystem. In effect, it converts
// oldname to an absolute path
if linkDir == "." {
linkDir = "/"
}

return filepath.Join(linkDir, oldname), newname
}

// writeCertImage takes imageRef and writes it to disk as JSON representing a pyxis.CertImage
// struct. The file is written at path certification.DefaultCertImageFilename.
//
Expand Down
15 changes: 15 additions & 0 deletions internal/engine/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,21 @@ var _ = Describe("Check Name Queries", func() {
})
})

var _ = Describe("Link Path Resolution", func() {
DescribeTable(
"Link targets should resolve correctly",
func(old, new, expectedOld, expectedNew string) {
resO, resN := resolveLinkPaths(old, new)
Expect(resO).To(Equal(expectedOld))
Expect(resN).To(Equal(expectedNew))
},
Entry("Link at root with relative origin", "../usr/lib/file", "file", "/usr/lib/file", "file"),
Entry("Origin is absolute", "/usr/lib/file", "file", "/usr/lib/file", "file"),
Entry("Link in dir with relative origin", "../usr/lib/file", "etc/file", "usr/lib/file", "etc/file"),
Entry("Link in dir with relative origin and up multiple levels", "../../cfg/file", "etc/foo/file", "cfg/file", "etc/foo/file"),
)
})

// writeTarball writes a tar archive to out with filename containing contents at the base path
// with extra bytes written at the end of length extraBytes.
// note: this should only be used as a helper function in tests
Expand Down

0 comments on commit d4dbf03

Please sign in to comment.