From a7687c861e2bf5e0a60bb6d096ed60b76decddd1 Mon Sep 17 00:00:00 2001 From: Lerk Date: Tue, 8 Mar 2022 01:30:40 +0100 Subject: [PATCH] Fix "invalid cross-device link" error when running on a different filesystem (#1769) * fix cross-device rename errors * fallback to copy instead of always using it * use tmp dir in data folder * recreate tmp dir at startup --- config/config.go | 2 ++ core/offlineState.go | 4 +-- core/transcoder/thumbnailGenerator.go | 4 +-- main.go | 11 ++++++++ utils/utils.go | 36 +++++++++++++++++++++++++-- 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/config/config.go b/config/config.go index df17cb0be..601769b3e 100644 --- a/config/config.go +++ b/config/config.go @@ -13,6 +13,8 @@ var DatabaseFilePath = "data/owncast.db" // LogDirectory is the path to various log files. var LogDirectory = "./data/logs" +var TempDir = "./data/tmp" + // EnableDebugFeatures will print additional data to help in debugging. var EnableDebugFeatures = false diff --git a/core/offlineState.go b/core/offlineState.go index 3ae512d04..e1257c37f 100644 --- a/core/offlineState.go +++ b/core/offlineState.go @@ -20,7 +20,7 @@ func appendOfflineToVariantPlaylist(index int, playlistFilePath string) { } tmpFileName := fmt.Sprintf("tmp-stream-%d.m3u8", index) - atomicWriteTmpPlaylistFile, err := os.CreateTemp(os.TempDir(), tmpFileName) + atomicWriteTmpPlaylistFile, err := os.CreateTemp(config.TempDir, tmpFileName) if err != nil { log.Errorln("error creating tmp playlist file to write to", playlistFilePath, err) return @@ -94,7 +94,7 @@ func createEmptyOfflinePlaylist(playlistFilePath string, offlineFilename string) func saveOfflineClipToDisk(offlineFilename string) (string, error) { offlineFileData := static.GetOfflineSegment() - offlineTmpFile, err := os.CreateTemp(os.TempDir(), offlineFilename) + offlineTmpFile, err := os.CreateTemp(config.TempDir, offlineFilename) if err != nil { log.Errorln("unable to create temp file for offline video segment", err) } diff --git a/core/transcoder/thumbnailGenerator.go b/core/transcoder/thumbnailGenerator.go index faa0c6a8b..102688005 100644 --- a/core/transcoder/thumbnailGenerator.go +++ b/core/transcoder/thumbnailGenerator.go @@ -106,7 +106,7 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error { } // rename temp file - if err := os.Rename(outputFileTemp, outputFile); err != nil { + if err := utils.Move(outputFileTemp, outputFile); err != nil { log.Errorln(err) } @@ -134,7 +134,7 @@ func makeAnimatedGifPreview(sourceFile string, outputFile string) { if _, err := exec.Command("sh", "-c", ffmpegCmd).Output(); err != nil { log.Errorln(err) // rename temp file - } else if err := os.Rename(outputFileTemp, outputFile); err != nil { + } else if err := utils.Move(outputFileTemp, outputFile); err != nil { log.Errorln(err) } } diff --git a/main.go b/main.go index 586ab5c48..567989b79 100644 --- a/main.go +++ b/main.go @@ -47,6 +47,17 @@ func main() { } } + // Recreate the temp dir + if utils.DoesFileExists(config.TempDir) { + err := os.RemoveAll(config.TempDir) + if err != nil { + log.Fatalln("Unable to remove temp dir!") + } + } + if err := os.Mkdir(config.TempDir, 0700); err != nil { + log.Fatalln("Unable to create temp dir!", err) + } + configureLogging(*enableDebugOptions, *enableVerboseLogging) log.Infoln(config.GetReleaseString()) diff --git a/utils/utils.go b/utils/utils.go index b02b1698c..15875ba05 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "io" "math/rand" "net/url" "os" @@ -60,9 +61,40 @@ func Copy(source, destination string) error { return os.WriteFile(destination, input, 0600) } -// Move moves the file to destination. +// Move moves the file at source to destination. func Move(source, destination string) error { - return os.Rename(source, destination) + err := os.Rename(source, destination) + if err != nil { + log.Warnln("Moving with os.Rename failed, falling back to copy and delete!", err) + return moveFallback(source, destination) + } + return nil +} + +// moveFallback moves a file using a copy followed by a delete, which works across file systems. +// source: https://gist.github.com/var23rav/23ae5d0d4d830aff886c3c970b8f6c6b +func moveFallback(source, destination string) error { + inputFile, err := os.Open(source) + if err != nil { + return fmt.Errorf("Couldn't open source file: %s", err) + } + outputFile, err := os.Create(destination) + if err != nil { + inputFile.Close() + return fmt.Errorf("Couldn't open dest file: %s", err) + } + defer outputFile.Close() + _, err = io.Copy(outputFile, inputFile) + inputFile.Close() + if err != nil { + return fmt.Errorf("Writing to output file failed: %s", err) + } + // The copy was successful, so now delete the original file + err = os.Remove(source) + if err != nil { + return fmt.Errorf("Failed removing original file: %s", err) + } + return nil } // IsUserAgentABot returns if a web client user-agent is seen as a bot.