|
7 | 7 | "os" |
8 | 8 | "path/filepath" |
9 | 9 | "strings" |
| 10 | + "syscall" |
10 | 11 |
|
11 | 12 | "github.com/0xJacky/Nginx-UI/internal/nginx" |
12 | 13 | "github.com/0xJacky/Nginx-UI/settings" |
@@ -383,35 +384,146 @@ func restoreNginxConfigs(nginxBackupDir string) error { |
383 | 384 | return ErrNginxConfigDirEmpty |
384 | 385 | } |
385 | 386 |
|
| 387 | + logger.Infof("Starting Nginx config restore from %s to %s", nginxBackupDir, destDir) |
| 388 | + |
386 | 389 | // Recursively clean destination directory preserving the directory structure |
| 390 | + logger.Info("Cleaning destination directory before restore") |
387 | 391 | if err := cleanDirectoryPreservingStructure(destDir); err != nil { |
| 392 | + logger.Errorf("Failed to clean directory %s: %v", destDir, err) |
388 | 393 | return cosy.WrapErrorWithParams(ErrCopyNginxConfigDir, "failed to clean directory: "+err.Error()) |
389 | 394 | } |
390 | 395 |
|
391 | 396 | // Copy files from backup to nginx config directory |
| 397 | + logger.Infof("Copying backup files to destination: %s", destDir) |
392 | 398 | if err := copyDirectory(nginxBackupDir, destDir); err != nil { |
| 399 | + logger.Errorf("Failed to copy backup files: %v", err) |
393 | 400 | return err |
394 | 401 | } |
395 | 402 |
|
| 403 | + logger.Info("Nginx config restore completed successfully") |
396 | 404 | return nil |
397 | 405 | } |
398 | 406 |
|
399 | | -// cleanDirectoryPreservingStructure removes all files and symlinks in a directory |
400 | | -// but preserves the directory structure itself |
| 407 | +// cleanDirectoryPreservingStructure removes all files and subdirectories in a directory |
| 408 | +// but preserves the directory structure itself and handles mount points correctly. |
401 | 409 | func cleanDirectoryPreservingStructure(dir string) error { |
| 410 | + logger.Infof("Cleaning directory: %s", dir) |
| 411 | + |
402 | 412 | entries, err := os.ReadDir(dir) |
403 | 413 | if err != nil { |
404 | 414 | return err |
405 | 415 | } |
406 | 416 |
|
407 | 417 | for _, entry := range entries { |
408 | 418 | path := filepath.Join(dir, entry.Name()) |
409 | | - err = os.RemoveAll(path) |
410 | | - if err != nil { |
| 419 | + |
| 420 | + if err := removeOrClearPath(path, entry.IsDir()); err != nil { |
411 | 421 | return err |
412 | 422 | } |
413 | 423 | } |
414 | 424 |
|
| 425 | + logger.Infof("Successfully cleaned directory: %s", dir) |
| 426 | + return nil |
| 427 | +} |
| 428 | + |
| 429 | +// removeOrClearPath removes a path or clears it if it's a mount point |
| 430 | +func removeOrClearPath(path string, isDir bool) error { |
| 431 | + // Try to remove the path first |
| 432 | + err := os.RemoveAll(path) |
| 433 | + if err == nil { |
| 434 | + return nil |
| 435 | + } |
| 436 | + |
| 437 | + // Handle removal failures |
| 438 | + if !isDeviceBusyError(err) { |
| 439 | + return fmt.Errorf("failed to remove %s: %w", path, err) |
| 440 | + } |
| 441 | + |
| 442 | + // Device busy - check if it's a mount point or directory |
| 443 | + if !isDir { |
| 444 | + return fmt.Errorf("file is busy and cannot be removed: %s: %w", path, err) |
| 445 | + } |
| 446 | + |
| 447 | + logger.Warnf("Path is busy (mount point): %s, clearing contents only", path) |
| 448 | + return clearDirectoryContents(path) |
| 449 | +} |
| 450 | + |
| 451 | +// isMountPoint checks if a path is a mount point by comparing device IDs |
| 452 | +// or checking /proc/mounts on Linux systems |
| 453 | +func isMountPoint(path string) bool { |
| 454 | + if isDeviceDifferent(path) { |
| 455 | + return true |
| 456 | + } |
| 457 | + |
| 458 | + return isInMountTable(path) |
| 459 | +} |
| 460 | + |
| 461 | +// isDeviceDifferent and isInMountTable are implemented in platform-specific files: |
| 462 | +// - restore_unix.go for Linux/Unix systems |
| 463 | +// - restore_windows.go for Windows systems |
| 464 | + |
| 465 | +// unescapeOctal converts octal escape sequences like \040 to their character equivalents |
| 466 | +func unescapeOctal(s string) string { |
| 467 | + var result strings.Builder |
| 468 | + |
| 469 | + for i := 0; i < len(s); i++ { |
| 470 | + if char, skip := tryParseOctal(s, i); skip > 0 { |
| 471 | + result.WriteByte(char) |
| 472 | + i += skip - 1 // -1 because loop will increment |
| 473 | + continue |
| 474 | + } |
| 475 | + result.WriteByte(s[i]) |
| 476 | + } |
| 477 | + |
| 478 | + return result.String() |
| 479 | +} |
| 480 | + |
| 481 | +// tryParseOctal attempts to parse octal sequence at position i |
| 482 | +// returns (char, skip) where skip > 0 if successful |
| 483 | +func tryParseOctal(s string, i int) (byte, int) { |
| 484 | + if s[i] != '\\' || i+3 >= len(s) { |
| 485 | + return 0, 0 |
| 486 | + } |
| 487 | + |
| 488 | + var char byte |
| 489 | + if _, err := fmt.Sscanf(s[i:i+4], "\\%03o", &char); err == nil { |
| 490 | + return char, 4 |
| 491 | + } |
| 492 | + |
| 493 | + return 0, 0 |
| 494 | +} |
| 495 | + |
| 496 | +// isDeviceBusyError checks if an error is a "device or resource busy" error |
| 497 | +func isDeviceBusyError(err error) bool { |
| 498 | + if err == nil { |
| 499 | + return false |
| 500 | + } |
| 501 | + |
| 502 | + if errno, ok := err.(syscall.Errno); ok && errno == syscall.EBUSY { |
| 503 | + return true |
| 504 | + } |
| 505 | + |
| 506 | + errMsg := err.Error() |
| 507 | + return strings.Contains(errMsg, "device or resource busy") || |
| 508 | + strings.Contains(errMsg, "resource busy") |
| 509 | +} |
| 510 | + |
| 511 | +// clearDirectoryContents removes all files and subdirectories within a directory |
| 512 | +// but preserves the directory itself. This is useful for cleaning mount points. |
| 513 | +func clearDirectoryContents(dir string) error { |
| 514 | + entries, err := os.ReadDir(dir) |
| 515 | + if err != nil { |
| 516 | + return err |
| 517 | + } |
| 518 | + |
| 519 | + for _, entry := range entries { |
| 520 | + path := filepath.Join(dir, entry.Name()) |
| 521 | + |
| 522 | + if err := removeOrClearPath(path, entry.IsDir()); err != nil { |
| 523 | + logger.Warnf("Failed to clear %s: %v, continuing", path, err) |
| 524 | + } |
| 525 | + } |
| 526 | + |
415 | 527 | return nil |
416 | 528 | } |
417 | 529 |
|
|
0 commit comments