Skip to content

Commit

Permalink
fix rename tmp file (fleetdm#4862)
Browse files Browse the repository at this point in the history
Renaming a temporary file to the final destination does not always work.
Specifically, if the source and destination paths are on different file
systems, you will get the following error

 invalid cross-device link

Instead, create temporary file in the destination directory.
  • Loading branch information
michalnicp authored Apr 1, 2022
1 parent 3575d34 commit d8d5827
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 6 deletions.
1 change: 1 addition & 0 deletions changes/issue-4792-download-tmp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Fix issue when renaming temporary files to another filesystem
39 changes: 33 additions & 6 deletions pkg/download/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,36 @@ import (

// Decompressed downloads and decompresses a file from a URL to a local path.
//
// It supports gz, bz2 and gz compressed files.
// It supports gz, bz2 and xz compressed files.
func Decompressed(client *http.Client, u url.URL, path string) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {

// atomically write to file
dir, file := filepath.Split(path)
if dir == "" {
// If the file is in the current working directory, then dir will be "".
// However, this means that ioutil.TempFile will use the default directory
// for temporary files, which is wrong.
dir = "."
}

// ensure dir exists
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
tmpFile, err := ioutil.TempFile("", fmt.Sprintf("%s*", filepath.Base(path)))

tmpFile, err := ioutil.TempFile(dir, file)
if err != nil {
return err
return fmt.Errorf("create temporary file: %w", err)
}
defer tmpFile.Close()
defer tmpFile.Close() // ignore err from closing twice

// Clean up tmp file if not moved
moved := false
defer func() {
if !moved {
os.Remove(tmpFile.Name())
}
}()

req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
Expand Down Expand Up @@ -57,14 +77,21 @@ func Decompressed(client *http.Client, u url.URL, path string) error {
default:
return fmt.Errorf("unknown extension: %s", u.Path)
}

if _, err := io.Copy(tmpFile, decompressor); err != nil {
return err
}

// Writes are not synchronous. Handle errors from writes returned by Close.
if err := tmpFile.Close(); err != nil {
return err
return fmt.Errorf("write and close temporary file: %w", err)
}

if err := os.Rename(tmpFile.Name(), path); err != nil {
return err
}

moved = true

return nil
}

0 comments on commit d8d5827

Please sign in to comment.