From 045ded60db36980079389c6714325dce134f9670 Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Wed, 15 May 2024 19:00:51 -0400 Subject: [PATCH] Fix export of symlinks from archives & OCI images (#209) --- CHANGELOG.md | 6 ++++ internal/app/forklift/bundles.go | 54 +++++++++++++++++++++----------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 298eed5d..64bcd6f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.7.2-alpha.5 - 2024-05-15 + +### Fixed + +- (cli) Symlinks are now handled correctly when they need to be exported from downloaded archives or OCI images. + ## 0.7.2-alpha.4 - 2024-05-15 ### Fixed diff --git a/internal/app/forklift/bundles.go b/internal/app/forklift/bundles.go index d66ae07d..109ba01e 100644 --- a/internal/app/forklift/bundles.go +++ b/internal/app/forklift/bundles.go @@ -365,11 +365,10 @@ func determineFileType( return filetype.MatchReader(archiveFile) } -func extractFile(tarReader *tar.Reader, sourcePath string, exportPath string) error { +func extractFile(tarReader *tar.Reader, sourcePath, exportPath string) error { if sourcePath == "/" || sourcePath == "." { sourcePath = "" } - fmt.Printf("exporting into %s...\n", exportPath) for { header, err := tarReader.Next() if err == io.EOF { @@ -391,27 +390,21 @@ func extractFile(tarReader *tar.Reader, sourcePath string, exportPath string) er case tar.TypeDir: if err = EnsureExists(filepath.FromSlash(targetPath)); err != nil { return errors.Wrapf( - err, "couldn't export %s from archive to %s", header.Name, targetPath, + err, "couldn't export directory %s from archive to %s", header.Name, targetPath, ) } case tar.TypeReg: - targetFile, err := os.OpenFile( - filepath.FromSlash(targetPath), os.O_RDWR|os.O_CREATE|os.O_TRUNC, - fs.FileMode(header.Mode&int64(fs.ModePerm)), - ) - if err != nil { - return errors.Wrapf(err, "couldn't create export file at %s", targetPath) + if err = extractRegularFile(header, tarReader, sourcePath, targetPath); err != nil { + return errors.Wrapf( + err, "couldn't export regular file %s from archive to %s", header.Name, targetPath, + ) } - defer func(file fs.File, filePath string) { - if err := file.Close(); err != nil { - // FIXME: handle this error better - fmt.Printf("Error: couldn't close export file %s\n", filePath) - } - }(targetFile, targetPath) - - if _, err = io.Copy(targetFile, tarReader); err != nil { + case tar.TypeSymlink: + if err = os.Symlink( + filepath.FromSlash(header.Linkname), filepath.FromSlash(targetPath), + ); err != nil { return errors.Wrapf( - err, "couldn't copy file %s in tar archive to %s", sourcePath, targetPath, + err, "couldn't export symlink %s from archive to %s", header.Name, targetPath, ) } } @@ -419,6 +412,31 @@ func extractFile(tarReader *tar.Reader, sourcePath string, exportPath string) er return nil } +func extractRegularFile( + header *tar.Header, tarReader *tar.Reader, sourcePath, targetPath string, +) error { + targetFile, err := os.OpenFile( + filepath.FromSlash(targetPath), os.O_RDWR|os.O_CREATE|os.O_TRUNC, + fs.FileMode(header.Mode&int64(fs.ModePerm)), + ) + if err != nil { + return errors.Wrapf(err, "couldn't create export file at %s", targetPath) + } + defer func(file fs.File, filePath string) { + if err := file.Close(); err != nil { + // FIXME: handle this error better + fmt.Printf("Error: couldn't close export file %s\n", filePath) + } + }(targetFile, targetPath) + + if _, err = io.Copy(targetFile, tarReader); err != nil { + return errors.Wrapf( + err, "couldn't copy file %s in tar archive to %s", sourcePath, targetPath, + ) + } + return nil +} + // FSBundle: FSRepoLoader func (b *FSBundle) LoadFSRepo(repoPath string, version string) (*core.FSRepo, error) {