forked from TrueCloudLab/restic
Set O_NOATIME flag on Linux
Citing Kerrisk, The Linux Programming Interface: The O_NOATIME flag is intended for use by indexing and backup programs. Its use can significantly reduce the amount of disk activity, because repeated disk seeks back and forth across the disk are not required to read the contents of a file and to update the last access time in the file’s i-node[.] restic used to do this, but the functionality was removed along with the fadvise call in #670.
This commit is contained in:
parent
2c3e5d943d
commit
7080fed7ae
4 changed files with 112 additions and 2 deletions
|
@ -85,7 +85,12 @@ func Create(name string) (*os.File, error) {
|
|||
|
||||
// Open opens a file for reading.
|
||||
func Open(name string) (File, error) {
|
||||
return os.Open(fixpath(name))
|
||||
f, err := os.Open(fixpath(name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setFlags(f)
|
||||
return f, err
|
||||
}
|
||||
|
||||
// OpenFile is the generalized open call; most users will use Open
|
||||
|
@ -94,7 +99,12 @@ func Open(name string) (File, error) {
|
|||
// methods on the returned File can be used for I/O.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
return os.OpenFile(fixpath(name), flag, perm)
|
||||
f, err := os.OpenFile(fixpath(name), flag, perm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setFlags(f)
|
||||
return f, err
|
||||
}
|
||||
|
||||
// Walk walks the file tree rooted at root, calling walkFn for each file or
|
||||
|
|
21
internal/fs/setflags_linux.go
Normal file
21
internal/fs/setflags_linux.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// SetFlags tries to set the O_NOATIME flag on f, which prevents the kernel
|
||||
// from updating the atime on a read call.
|
||||
//
|
||||
// The call fails when we're not the owner of the file or root. The caller
|
||||
// should ignore the error, which is returned for testing only.
|
||||
func setFlags(f *os.File) error {
|
||||
fd := f.Fd()
|
||||
flags, err := unix.FcntlInt(fd, unix.F_GETFL, 0)
|
||||
if err == nil {
|
||||
_, err = unix.FcntlInt(fd, unix.F_SETFL, flags|unix.O_NOATIME)
|
||||
}
|
||||
return err
|
||||
}
|
67
internal/fs/setflags_linux_test.go
Normal file
67
internal/fs/setflags_linux_test.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TestNoatime(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "restic-test-noatime")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
err = Remove(f.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Only run this test on common filesystems that support O_NOATIME.
|
||||
// On others, we may not get an error.
|
||||
if !supportsNoatime(t, f) {
|
||||
t.Skip("temp directory may not support O_NOATIME, skipping")
|
||||
}
|
||||
// From this point on, we own the file, so we should not get EPERM.
|
||||
|
||||
_, err = io.WriteString(f, "Hello!")
|
||||
rtest.OK(t, err)
|
||||
_, err = f.Seek(0, io.SeekStart)
|
||||
rtest.OK(t, err)
|
||||
|
||||
getAtime := func() time.Time {
|
||||
info, err := f.Stat()
|
||||
rtest.OK(t, err)
|
||||
return ExtendedStat(info).AccessTime
|
||||
}
|
||||
|
||||
atime := getAtime()
|
||||
|
||||
err = setFlags(f)
|
||||
rtest.OK(t, err)
|
||||
|
||||
_, err = f.Read(make([]byte, 1))
|
||||
rtest.OK(t, err)
|
||||
rtest.Equals(t, atime, getAtime())
|
||||
}
|
||||
|
||||
func supportsNoatime(t *testing.T, f *os.File) bool {
|
||||
var fsinfo unix.Statfs_t
|
||||
err := unix.Fstatfs(int(f.Fd()), &fsinfo)
|
||||
rtest.OK(t, err)
|
||||
|
||||
return fsinfo.Type == unix.BTRFS_SUPER_MAGIC ||
|
||||
fsinfo.Type == unix.EXT2_SUPER_MAGIC ||
|
||||
fsinfo.Type == unix.EXT3_SUPER_MAGIC ||
|
||||
fsinfo.Type == unix.EXT4_SUPER_MAGIC ||
|
||||
fsinfo.Type == unix.TMPFS_MAGIC
|
||||
}
|
12
internal/fs/setflags_other.go
Normal file
12
internal/fs/setflags_other.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package fs
|
||||
|
||||
import "os"
|
||||
|
||||
// OS-specific replacements of setFlags can set file status flags
|
||||
// that improve I/O performance.
|
||||
func setFlags(*os.File) error {
|
||||
return nil
|
||||
}
|
Loading…
Reference in a new issue