123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384 |
- /*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
- package main
- import (
- "fmt"
- "io/fs"
- "os"
- "sync"
- )
- var FileSysInfoMutex = &sync.Mutex{}
- /*
- Try to detect whether the file was rotated.
- As is the case with logrotate, we assume there are only a
- couple ways to perform file rotation: rename then create, or
- copy then, usually, truncate.
- See `man 8 logrotate` for details.
- */
- func detectFileRotation(
- path string,
- file *os.File,
- ) (rot bool, err error) {
- /*
- The order in which we retrieve this file info matters.
- Suppose an empty file is rotated by copytruncate after
- 24 hours. The inode is unchanged. The file size is
- unchanged. Without subscribing to some kernel-level
- stream of filesystem events, all we can do is check for
- metadata modification (ctime) to see whether the new
- file has a ctime that is greater than the ctime of the
- old file.
- But what if the file hasn't been rotated? We test for
- rotation and observe that, again, the inode and size are
- unchanged. However, the ctime could have been updated by
- a write while we were awaiting our syscall. Then we
- would notice a new ctime for the same file, which would
- prompt us to frivolously reopen the file.
- Is this false positive so bad? Well, yes, it would be
- more costly for busier logs.
- Therefore, we propose to retrieve the "new" ctime,
- first, then retrieve the "current" ctime for the
- open file. This way, we never run the risk have a false
- positive.
- That said, if the Go compiler or CPU changes the order
- of these calls, we are properly horked. Therefore we opt
- to use sync.Mutex to guarantee the correct order. See
- https://go.dev/ref/mem#locks for details.
- */
- var curInfo, newInfo fs.FileInfo
- FileSysInfoMutex.Lock()
- if newInfo, err = os.Stat(path); err != nil {
- err = fmt.Errorf("unable to retrieve info for file %#v: %w", path, err)
- return
- }
- FileSysInfoMutex.Unlock()
- FileSysInfoMutex.Lock()
- if curInfo, err = file.Stat(); err != nil {
- err = fmt.Errorf("unable to retrieve info for file %#v: %w", path, err)
- return
- }
- FileSysInfoMutex.Unlock()
- var curStat, newStat FileSysInfo
- var ok bool
- if curStat, ok = curInfo.Sys().(FileSysInfo); !ok {
- panic(fmt.Sprintf("BUG: expected %s for open file but got type %T instead", FileSysInfoStr, curInfo.Sys()))
- }
- if newStat, ok = newInfo.Sys().(FileSysInfo); !ok {
- panic(fmt.Sprintf("BUG: expected %s for rotated file but got type %T instead", FileSysInfoStr, curInfo.Sys()))
- }
- return fileWasRotated(newStat, curStat), err
- }
|