rotation.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. /*
  2. * This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  5. */
  6. package main
  7. import (
  8. "fmt"
  9. "io/fs"
  10. "os"
  11. "sync"
  12. )
  13. var FileSysInfoMutex = &sync.Mutex{}
  14. /*
  15. Try to detect whether the file was rotated.
  16. As is the case with logrotate, we assume there are only a
  17. couple ways to perform file rotation: rename then create, or
  18. copy then, usually, truncate.
  19. See `man 8 logrotate` for details.
  20. */
  21. func detectFileRotation(
  22. path string,
  23. file *os.File,
  24. ) (rot bool, err error) {
  25. /*
  26. The order in which we retrieve this file info matters.
  27. Suppose an empty file is rotated by copytruncate after
  28. 24 hours. The inode is unchanged. The file size is
  29. unchanged. Without subscribing to some kernel-level
  30. stream of filesystem events, all we can do is check for
  31. metadata modification (ctime) to see whether the new
  32. file has a ctime that is greater than the ctime of the
  33. old file.
  34. But what if the file hasn't been rotated? We test for
  35. rotation and observe that, again, the inode and size are
  36. unchanged. However, the ctime could have been updated by
  37. a write while we were awaiting our syscall. Then we
  38. would notice a new ctime for the same file, which would
  39. prompt us to frivolously reopen the file.
  40. Is this false positive so bad? Well, yes, it would be
  41. more costly for busier logs.
  42. Therefore, we propose to retrieve the "new" ctime,
  43. first, then retrieve the "current" ctime for the
  44. open file. This way, we never run the risk have a false
  45. positive.
  46. That said, if the Go compiler or CPU changes the order
  47. of these calls, we are properly horked. Therefore we opt
  48. to use sync.Mutex to guarantee the correct order. See
  49. https://go.dev/ref/mem#locks for details.
  50. */
  51. var curInfo, newInfo fs.FileInfo
  52. FileSysInfoMutex.Lock()
  53. if newInfo, err = os.Stat(path); err != nil {
  54. err = fmt.Errorf("unable to retrieve info for file %#v: %w", path, err)
  55. return
  56. }
  57. FileSysInfoMutex.Unlock()
  58. FileSysInfoMutex.Lock()
  59. if curInfo, err = file.Stat(); err != nil {
  60. err = fmt.Errorf("unable to retrieve info for file %#v: %w", path, err)
  61. return
  62. }
  63. FileSysInfoMutex.Unlock()
  64. var curStat, newStat FileSysInfo
  65. var ok bool
  66. if curStat, ok = curInfo.Sys().(FileSysInfo); !ok {
  67. panic(fmt.Sprintf("BUG: expected %s for open file but got type %T instead", FileSysInfoStr, curInfo.Sys()))
  68. }
  69. if newStat, ok = newInfo.Sys().(FileSysInfo); !ok {
  70. panic(fmt.Sprintf("BUG: expected %s for rotated file but got type %T instead", FileSysInfoStr, curInfo.Sys()))
  71. }
  72. return fileWasRotated(newStat, curStat), err
  73. }