Implements copy-on-write for TUS uploads

Implements a copy-on-write approach for handling TUS uploads.

This change addresses potential issues with direct file modification by introducing a temporary file for writing incoming data. The temporary file is used to accumulate the changes, and then the content is copied to the destination file upon completion. This ensures data integrity and avoids potential corruption during the upload process.
pull/4878/head
Hippo Lin 2025-05-29 06:57:18 +00:00
parent cfea84fd5e
commit 59c26eaa80
No known key found for this signature in database
GPG Key ID: 63F230BBABF79048
1 changed files with 54 additions and 10 deletions

View File

@ -131,21 +131,65 @@ func tusPatchHandler() handleFunc {
)
}
openFile, err := d.user.Fs.OpenFile(r.URL.Path, os.O_WRONLY|os.O_APPEND, files.PermFile)
if err != nil {
return http.StatusInternalServerError, fmt.Errorf("could not open file: %w", err)
}
defer openFile.Close()
// Create a temp file in /tmp directory for copy-on-write approach
tempDir := "/tmp"
tempFilePath := filepath.Join(tempDir, "filebrowser_"+filepath.Base(r.URL.Path)+"_"+strconv.FormatInt(uploadOffset, 10))
_, err = openFile.Seek(uploadOffset, 0)
tempFile, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_WRONLY, files.PermFile)
if err != nil {
return http.StatusInternalServerError, fmt.Errorf("could not seek file: %w", err)
return http.StatusInternalServerError, fmt.Errorf("could not create temp file: %w", err)
}
defer func() {
tempFile.Close()
os.Remove(tempFilePath) // Clean up temp file after use
}()
// If we're not starting from zero, copy the existing content
if uploadOffset > 0 {
srcFile, err := d.user.Fs.Open(r.URL.Path)
if err != nil {
return http.StatusInternalServerError, fmt.Errorf("could not open source file: %w", err)
}
_, err = io.Copy(tempFile, srcFile)
srcFile.Close()
if err != nil {
return http.StatusInternalServerError, fmt.Errorf("could not copy existing content: %w", err)
}
}
defer r.Body.Close()
bytesWritten, err := io.Copy(openFile, r.Body)
// Write the new content to the temp file
_, err = tempFile.Seek(uploadOffset, 0)
if err != nil {
return http.StatusInternalServerError, fmt.Errorf("could not write to file: %w", err)
return http.StatusInternalServerError, fmt.Errorf("could not seek temp file: %w", err)
}
bytesWritten, err := io.Copy(tempFile, r.Body)
r.Body.Close()
if err != nil {
return http.StatusInternalServerError, fmt.Errorf("could not write to temp file: %w", err)
}
// Close the temp file to ensure all data is flushed
tempFile.Close()
// Open the temp file for reading
tempFile, err = os.Open(tempFilePath)
if err != nil {
return http.StatusInternalServerError, fmt.Errorf("could not open temp file for reading: %w", err)
}
defer tempFile.Close()
// Create/truncate the destination file and copy the content from temp file
destFile, err := d.user.Fs.OpenFile(r.URL.Path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, files.PermFile)
if err != nil {
return http.StatusInternalServerError, fmt.Errorf("could not open destination file: %w", err)
}
defer destFile.Close()
_, err = io.Copy(destFile, tempFile)
if err != nil {
return http.StatusInternalServerError, fmt.Errorf("could not copy from temp to destination: %w", err)
}
w.Header().Set("Upload-Offset", strconv.FormatInt(uploadOffset+bytesWritten, 10))