1// SPDX-License-Identifier: MIT
2
3// Package fsys is the minimal file-system abstraction used by the store, with
4// an os-backed implementation. The faultfs sub-package provides an in-memory
5// implementation with fault injection for tests.
6package fsys
7
8import (
9 "errors"
10 "io"
11 "os"
12 "sort"
13 "syscall"
14)
15
16// ErrLocked is returned by FS.Lock when the lock is already held.
17var ErrLocked = errors.New("fsys: locked")
18
19// FS is a file system. Paths are used as given; implementations do not
20// resolve them against a working directory other than the OS one.
21type FS interface {
22 // OpenFile opens name with os.OpenFile flags. Files opened for writing
23 // always append: Write adds bytes at the current end of the file.
24 OpenFile(name string, flag int, perm os.FileMode) (File, error)
25 // Rename atomically replaces newpath with oldpath.
26 Rename(oldpath, newpath string) error
27 // Remove deletes a file.
28 Remove(name string) error
29 // ReadDir returns the sorted entry names of dir.
30 ReadDir(dir string) ([]string, error)
31 // MkdirAll creates dir and any missing parents.
32 MkdirAll(dir string, perm os.FileMode) error
33 // SyncDir makes the entries of dir (creations, renames, removals)
34 // durable.
35 SyncDir(dir string) error
36 // Lock takes an exclusive, non-blocking lock on the file at path,
37 // creating it if needed. It returns ErrLocked if the lock is held,
38 // including by the same process. Closing the result releases the lock.
39 Lock(path string) (io.Closer, error)
40}
41
42// File is an open file.
43type File interface {
44 // Write appends p to the end of the file.
45 Write(p []byte) (int, error)
46 ReadAt(p []byte, off int64) (int, error)
47 // Sync flushes data and the size needed to read it (fdatasync).
48 Sync() error
49 // SyncAll flushes data and all metadata (fsync).
50 SyncAll() error
51 Truncate(size int64) error
52 Size() (int64, error)
53 Close() error
54}
55
56// OS returns the FS backed by the operating system.
57func OS() FS { return osFS{} }
58
59type osFS struct{}
60
61func (osFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
62 if flag&(os.O_WRONLY|os.O_RDWR) != 0 {
63 flag |= os.O_APPEND
64 }
65 f, err := os.OpenFile(name, flag, perm)
66 if err != nil {
67 return nil, err
68 }
69 return osFile{f}, nil
70}
71
72func (osFS) Rename(oldpath, newpath string) error { return os.Rename(oldpath, newpath) }
73
74func (osFS) Remove(name string) error { return os.Remove(name) }
75
76func (osFS) ReadDir(dir string) ([]string, error) {
77 entries, err := os.ReadDir(dir)
78 if err != nil {
79 return nil, err
80 }
81 names := make([]string, len(entries))
82 for i, e := range entries {
83 names[i] = e.Name()
84 }
85 sort.Strings(names)
86 return names, nil
87}
88
89func (osFS) MkdirAll(dir string, perm os.FileMode) error { return os.MkdirAll(dir, perm) }
90
91func (osFS) SyncDir(dir string) error {
92 d, err := os.Open(dir)
93 if err != nil {
94 return err
95 }
96 err = d.Sync()
97 if cerr := d.Close(); err == nil {
98 err = cerr
99 }
100 return err
101}
102
103func (osFS) Lock(path string) (io.Closer, error) {
104 f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o600)
105 if err != nil {
106 return nil, err
107 }
108 if err := control(f, func(fd int) error {
109 return syscall.Flock(fd, syscall.LOCK_EX|syscall.LOCK_NB)
110 }); err != nil {
111 _ = f.Close()
112 if errors.Is(err, syscall.EWOULDBLOCK) {
113 return nil, ErrLocked
114 }
115 return nil, err
116 }
117 return f, nil // closing the descriptor releases the flock
118}
119
120type osFile struct{ *os.File }
121
122func (f osFile) Sync() error {
123 return control(f.File, syscall.Fdatasync)
124}
125
126func (f osFile) SyncAll() error { return f.File.Sync() }
127
128func (f osFile) Size() (int64, error) {
129 fi, err := f.Stat()
130 if err != nil {
131 return 0, err
132 }
133 return fi.Size(), nil
134}
135
136// control runs fn on the file descriptor, retrying on EINTR.
137func control(f *os.File, fn func(fd int) error) error {
138 rc, err := f.SyscallConn()
139 if err != nil {
140 return err
141 }
142 var ferr error
143 if err := rc.Control(func(fd uintptr) {
144 for {
145 ferr = fn(int(fd))
146 if !errors.Is(ferr, syscall.EINTR) {
147 return
148 }
149 }
150 }); err != nil {
151 return err
152 }
153 return ferr
154}