github.com/zuoyebang/bitalosdb@v1.1.1-0.20240516111551-79a8c4d8ce20/internal/vfs/vfs.go (about) 1 // Copyright 2021 The Bitalosdb author(hustxrb@163.com) and other contributors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package vfs 16 17 import ( 18 "io" 19 "os" 20 "path/filepath" 21 "syscall" 22 23 "github.com/cockroachdb/errors" 24 "github.com/cockroachdb/errors/oserror" 25 ) 26 27 // File is a readable, writable sequence of bytes. 28 // 29 // Typically, it will be an *os.File, but test code may choose to substitute 30 // memory-backed implementations. 31 type File interface { 32 io.Closer 33 io.Reader 34 io.ReaderAt 35 io.Writer 36 io.Seeker 37 Stat() (os.FileInfo, error) 38 Sync() error 39 } 40 41 // OpenOption provide an interface to do work on file handles in the Open() 42 // call. 43 type OpenOption interface { 44 // Apply is called on the file handle after it's opened. 45 Apply(File) 46 } 47 48 // FS is a namespace for files. 49 // 50 // The names are filepath names: they may be / separated or \ separated, 51 // depending on the underlying operating system. 52 type FS interface { 53 // Create creates the named file for reading and writing. If a file 54 // already exists at the provided name, it's removed first ensuring the 55 // resulting file descriptor points to a new inode. 56 Create(name string) (File, error) 57 58 // Link creates newname as a hard link to the oldname file. 59 Link(oldname, newname string) error 60 61 // Open opens the named file for reading. openOptions provides 62 Open(name string, opts ...OpenOption) (File, error) 63 64 // OpenDir opens the named directory for syncing. 65 OpenDir(name string) (File, error) 66 67 // Remove removes the named file or directory. 68 Remove(name string) error 69 70 // RemoveAll removes the named file or directory and any children it 71 // contains. It removes everything it can but returns the first error it 72 // encounters. 73 RemoveAll(name string) error 74 75 // Rename renames a file. It overwrites the file at newname if one exists, 76 // the same as os.Rename. 77 Rename(oldname, newname string) error 78 79 // ReuseForWrite attempts to reuse the file with oldname by renaming it to newname and opening 80 // it for writing without truncation. It is acceptable for the implementation to choose not 81 // to reuse oldname, and simply create the file with newname -- in this case the implementation 82 // should delete oldname. If the caller calls this function with an oldname that does not exist, 83 // the implementation may return an error. 84 ReuseForWrite(oldname, newname string) (File, error) 85 86 OpenForWrite(name string) (File, error) 87 88 OpenWR(name string) (File, error) 89 90 // MkdirAll creates a directory and all necessary parents. The permission 91 // bits perm have the same semantics as in os.MkdirAll. If the directory 92 // already exists, MkdirAll does nothing and returns nil. 93 MkdirAll(dir string, perm os.FileMode) error 94 95 // Lock locks the given file, creating the file if necessary, and 96 // truncating the file if it already exists. The lock is an exclusive lock 97 // (a write lock), but locked files should neither be read from nor written 98 // to. Such files should have zero size and only exist to co-ordinate 99 // ownership across processes. 100 // 101 // A nil Closer is returned if an error occurred. Otherwise, close that 102 // Closer to release the lock. 103 // 104 // On Linux and OSX, a lock has the same semantics as fcntl(2)'s advisory 105 // locks. In particular, closing any other file descriptor for the same 106 // file will release the lock prematurely. 107 // 108 // Attempting to lock a file that is already locked by the current process 109 // returns an error and leaves the existing lock untouched. 110 // 111 // Lock is not yet implemented on other operating systems, and calling it 112 // will return an error. 113 Lock(name string) (io.Closer, error) 114 115 // List returns a listing of the given directory. The names returned are 116 // relative to dir. 117 List(dir string) ([]string, error) 118 119 // Stat returns an os.FileInfo describing the named file. 120 Stat(name string) (os.FileInfo, error) 121 122 // PathBase returns the last element of path. Trailing path separators are 123 // removed before extracting the last element. If the path is empty, PathBase 124 // returns ".". If the path consists entirely of separators, PathBase returns a 125 // single separator. 126 PathBase(path string) string 127 128 // PathJoin joins any number of path elements into a single path, adding a 129 // separator if necessary. 130 PathJoin(elem ...string) string 131 132 // PathDir returns all but the last element of path, typically the path's directory. 133 PathDir(path string) string 134 135 // GetDiskUsage returns disk space statistics for the filesystem where 136 // path is any file or directory within that filesystem. 137 GetDiskUsage(path string) (DiskUsage, error) 138 } 139 140 // DiskUsage summarizes disk space usage on a filesystem. 141 type DiskUsage struct { 142 // Total disk space available to the current process in bytes. 143 AvailBytes uint64 144 // Total disk space in bytes. 145 TotalBytes uint64 146 // Used disk space in bytes. 147 UsedBytes uint64 148 } 149 150 // Default is a FS implementation backed by the underlying operating system's 151 // file system. 152 var Default FS = defaultFS{} 153 154 type defaultFS struct{} 155 156 func (defaultFS) Create(name string) (File, error) { 157 const openFlags = os.O_RDWR | os.O_CREATE | os.O_EXCL | syscall.O_CLOEXEC 158 159 f, err := os.OpenFile(name, openFlags, 0666) 160 // If the file already exists, remove it and try again. 161 // 162 // NB: We choose to remove the file instead of truncating it, despite the 163 // fact that we can't do so atomically, because it's more resistant to 164 // misuse when using hard links. 165 166 // We must loop in case another goroutine/thread/process is also 167 // attempting to create the a file at the same path. 168 for oserror.IsExist(err) { 169 if removeErr := os.Remove(name); removeErr != nil && !oserror.IsNotExist(removeErr) { 170 return f, errors.WithStack(removeErr) 171 } 172 f, err = os.OpenFile(name, openFlags, 0666) 173 } 174 return f, errors.WithStack(err) 175 } 176 177 func (defaultFS) Link(oldname, newname string) error { 178 return errors.WithStack(os.Link(oldname, newname)) 179 } 180 181 func (defaultFS) Open(name string, opts ...OpenOption) (File, error) { 182 file, err := os.OpenFile(name, os.O_RDONLY|syscall.O_CLOEXEC, 0) 183 if err != nil { 184 return nil, errors.WithStack(err) 185 } 186 for _, opt := range opts { 187 opt.Apply(file) 188 } 189 return file, nil 190 } 191 192 func (defaultFS) Remove(name string) error { 193 return errors.WithStack(os.Remove(name)) 194 } 195 196 func (defaultFS) RemoveAll(name string) error { 197 return errors.WithStack(os.RemoveAll(name)) 198 } 199 200 func (defaultFS) Rename(oldname, newname string) error { 201 return errors.WithStack(os.Rename(oldname, newname)) 202 } 203 204 func (fs defaultFS) ReuseForWrite(oldname, newname string) (File, error) { 205 if err := fs.Rename(oldname, newname); err != nil { 206 return nil, errors.WithStack(err) 207 } 208 f, err := os.OpenFile(newname, os.O_RDWR|os.O_CREATE|syscall.O_CLOEXEC, 0666) 209 return f, errors.WithStack(err) 210 } 211 212 func (fs defaultFS) OpenForWrite(name string) (File, error) { 213 f, err := os.OpenFile(name, os.O_RDWR|os.O_APPEND|syscall.O_CLOEXEC, 0666) 214 return f, errors.WithStack(err) 215 } 216 217 func (fs defaultFS) OpenWR(name string) (File, error) { 218 f, err := os.OpenFile(name, os.O_RDWR|syscall.O_CLOEXEC, 0666) 219 return f, errors.WithStack(err) 220 } 221 222 func (defaultFS) MkdirAll(dir string, perm os.FileMode) error { 223 return errors.WithStack(os.MkdirAll(dir, perm)) 224 } 225 226 func (defaultFS) List(dir string) ([]string, error) { 227 f, err := os.Open(dir) 228 if err != nil { 229 return nil, err 230 } 231 defer f.Close() 232 dirnames, err := f.Readdirnames(-1) 233 return dirnames, errors.WithStack(err) 234 } 235 236 func (defaultFS) Stat(name string) (os.FileInfo, error) { 237 finfo, err := os.Stat(name) 238 return finfo, errors.WithStack(err) 239 } 240 241 func (defaultFS) PathBase(path string) string { 242 return filepath.Base(path) 243 } 244 245 func (defaultFS) PathJoin(elem ...string) string { 246 return filepath.Join(elem...) 247 } 248 249 func (defaultFS) PathDir(path string) string { 250 return filepath.Dir(path) 251 } 252 253 type sequentialReadsOption struct{} 254 255 // SequentialReadsOption is an OpenOption that optimizes opened file handle for 256 // sequential reads, by calling fadvise() with POSIX_FADV_SEQUENTIAL on Linux 257 // systems to enable readahead. 258 var SequentialReadsOption OpenOption = &sequentialReadsOption{} 259 260 // Apply implements the OpenOption interface. 261 func (sequentialReadsOption) Apply(f File) { 262 type fd interface { 263 Fd() uintptr 264 } 265 if fdFile, ok := f.(fd); ok { 266 _ = fadviseSequential(fdFile.Fd()) 267 } 268 } 269 270 // Copy copies the contents of oldname to newname. If newname exists, it will 271 // be overwritten. 272 func Copy(fs FS, oldname, newname string) error { 273 src, err := fs.Open(oldname) 274 if err != nil { 275 return err 276 } 277 defer src.Close() 278 279 dst, err := fs.Create(newname) 280 if err != nil { 281 return err 282 } 283 defer dst.Close() 284 285 if _, err := io.Copy(dst, src); err != nil { 286 return err 287 } 288 return dst.Sync() 289 } 290 291 // LinkOrCopy creates newname as a hard link to the oldname file. If creating 292 // the hard link fails, LinkOrCopy falls back to copying the file (which may 293 // also fail if newname doesn't exist or oldname already exists). 294 func LinkOrCopy(fs FS, oldname, newname string) error { 295 err := fs.Link(oldname, newname) 296 if err == nil { 297 return nil 298 } 299 // Permit a handful of errors which we know won't be fixed by copying the 300 // file. Note that we don't check for the specifics of the error code as it 301 // isn't easy to do so in a portable manner. On Unix we'd have to check for 302 // LinkError.Err == syscall.EXDEV. On Windows we'd have to check for 303 // ERROR_NOT_SAME_DEVICE, ERROR_INVALID_FUNCTION, and 304 // ERROR_INVALID_PARAMETER. Rather that such OS specific checks, we fall back 305 // to always trying to copy if hard-linking failed. 306 if oserror.IsExist(err) || oserror.IsNotExist(err) || oserror.IsPermission(err) { 307 return err 308 } 309 return Copy(fs, oldname, newname) 310 }