osutil.go 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. // Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
  2. // All rights reserved. Use of this source code is governed by an MIT-style
  3. // license that can be found in the LICENSE file.
  4. // Package osutil implements utilities for native OS support.
  5. package osutil
  6. import (
  7. "os"
  8. "path/filepath"
  9. "runtime"
  10. "sync"
  11. )
  12. // Try to keep this entire operation atomic-like. We shouldn't be doing this
  13. // often enough that there is any contention on this lock.
  14. var renameLock sync.Mutex
  15. // Rename renames a file, while trying hard to succeed on various systems by
  16. // temporarily tweaking directory permissions and removing the destination
  17. // file when necessary. Will make sure to delete the from file if the
  18. // operation fails, so use only for situations like committing a temp file to
  19. // it's final location.
  20. func Rename(from, to string) error {
  21. renameLock.Lock()
  22. defer renameLock.Unlock()
  23. // Make sure the destination directory is writeable
  24. toDir := filepath.Dir(to)
  25. if info, err := os.Stat(toDir); err == nil {
  26. os.Chmod(toDir, 0777)
  27. defer os.Chmod(toDir, info.Mode())
  28. }
  29. // On Windows, make sure the destination file is writeable (or we can't delete it)
  30. if runtime.GOOS == "windows" {
  31. os.Chmod(to, 0666)
  32. err := os.Remove(to)
  33. if err != nil && !os.IsNotExist(err) {
  34. return err
  35. }
  36. }
  37. // Don't leave a dangling temp file in case of rename error
  38. defer os.Remove(from)
  39. return os.Rename(from, to)
  40. }
  41. // InWritableDir calls fn(path), while making sure that the directory
  42. // containing `path` is writable for the duration of the call.
  43. func InWritableDir(fn func(string) error, path string) error {
  44. dir := filepath.Dir(path)
  45. if info, err := os.Stat(dir); err == nil && info.IsDir() && info.Mode()&04 == 0 {
  46. // A non-writeable directory (for this user; we assume that's the
  47. // relevant part). Temporarily change the mode so we can delete the
  48. // file or directory inside it.
  49. err = os.Chmod(dir, 0755)
  50. if err == nil {
  51. defer func() {
  52. err = os.Chmod(dir, info.Mode())
  53. if err != nil {
  54. // We managed to change the permission bits like a
  55. // millisecond ago, so it'd be bizarre if we couldn't
  56. // change it back.
  57. panic(err)
  58. }
  59. }()
  60. }
  61. }
  62. return fn(path)
  63. }