Skip to content

Commit badb6ef

Browse files
authored
Merge pull request #676 from Zephyrcf/fix/fix_ci_busy_file
[Fix] use atomic symlink for nydusd binary upgrades
2 parents 630ce1f + 0784b5a commit badb6ef

File tree

1 file changed

+32
-20
lines changed

1 file changed

+32
-20
lines changed

pkg/system/system.go

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -348,15 +348,12 @@ func (sc *Controller) upgradeDaemons() func(w http.ResponseWriter, r *http.Reque
348348
sourcePath := c.NydusdPath
349349
destinationPath := manager.NydusdBinaryPath
350350

351-
if err = copyNydusdBinary(sourcePath, destinationPath); err != nil {
351+
if err = upgradeNydusdWithSymlink(sourcePath, destinationPath); err != nil {
352352
log.L.Errorf("Failed to copy nydusd binary from %s to %s: %v",
353353
sourcePath, destinationPath, err)
354354
statusCode = http.StatusInternalServerError
355355
return
356356
}
357-
358-
log.L.Infof("Successfully copy nydusd binary from %s to %s",
359-
sourcePath, destinationPath)
360357
}
361358
}
362359
}
@@ -470,36 +467,51 @@ func buildNextAPISocket(cur string) (string, error) {
470467
return nextSocket, nil
471468
}
472469

473-
// copyNydusdBinary copies a file from sourcePath to destinationPath,
474-
// ensuring parent directories exist and setting 0755 permissions.
475-
// It overwrites the destination file if it already exists.
476-
func copyNydusdBinary(sourcePath, destinationPath string) error {
477-
fileMode := os.FileMode(0755)
470+
// upgradeNydusdWithSymlink atomically creates a symbolic link from destinationPath to sourcePath.
471+
// It uses atomic rename to avoid any gap period where the destination doesn't exist.
472+
// Running processes are not affected as they hold file descriptors to the original inode.
473+
func upgradeNydusdWithSymlink(sourcePath, destinationPath string) error {
474+
// Ensure source file exists and is accessible
475+
if _, err := os.Stat(sourcePath); err != nil {
476+
return fmt.Errorf("source file %s does not exist or is not accessible: %w", sourcePath, err)
477+
}
478478

479+
// Ensure destination directory exists
479480
destDir := filepath.Dir(destinationPath)
480-
if err := os.MkdirAll(destDir, fileMode); err != nil {
481+
if err := os.MkdirAll(destDir, 0755); err != nil {
481482
return fmt.Errorf("failed to create destination directory %s: %w", destDir, err)
482483
}
483484

484-
sourceFile, err := os.Open(sourcePath)
485+
// Use absolute path for source to avoid relative path issues
486+
absSourcePath, err := filepath.Abs(sourcePath)
485487
if err != nil {
486-
return fmt.Errorf("failed to open source file %s: %w", sourcePath, err)
488+
return fmt.Errorf("failed to get absolute path for %s: %w", sourcePath, err)
487489
}
488-
defer sourceFile.Close()
489490

490-
destinationFile, err := os.OpenFile(destinationPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, fileMode)
491+
// Get absolute path for destination to compare
492+
absDestinationPath, err := filepath.Abs(destinationPath)
491493
if err != nil {
492-
return fmt.Errorf("failed to create destination file %s: %w", destinationPath, err)
494+
return fmt.Errorf("failed to get absolute path for %s: %w", destinationPath, err)
495+
}
496+
497+
// Check if source and destination are the same to avoid circular symlink
498+
if absSourcePath == absDestinationPath {
499+
return fmt.Errorf("source path and destination path are the same: %s", absSourcePath)
493500
}
494-
defer destinationFile.Close()
495501

496-
if _, err := io.Copy(destinationFile, sourceFile); err != nil {
497-
return fmt.Errorf("failed to copy file contents from %s to %s: %w", sourcePath, destinationPath, err)
502+
// Create a temporary symbolic link first
503+
tempSymlinkPath := absDestinationPath + ".tmp"
504+
if err := os.Symlink(absSourcePath, tempSymlinkPath); err != nil {
505+
return fmt.Errorf("failed to create temporary symbolic link %s: %w", tempSymlinkPath, err)
498506
}
499507

500-
if err := os.Chmod(destinationPath, fileMode); err != nil {
501-
return fmt.Errorf("failed to set permissions for %s to %s: %w", destinationPath, fileMode.String(), err)
508+
// Atomically replace the destination with the temporary symlink
509+
if err := os.Rename(tempSymlinkPath, absDestinationPath); err != nil {
510+
// Clean up temporary symlink if rename fails
511+
os.Remove(tempSymlinkPath)
512+
return fmt.Errorf("failed to atomically replace symlink %s: %w", absDestinationPath, err)
502513
}
503514

515+
log.L.Infof("Successfully created symbolic link from %s to %s", absDestinationPath, absSourcePath)
504516
return nil
505517
}

0 commit comments

Comments
 (0)