github.com/searKing/golang/go@v1.2.117/sync/filelock/filelock_osfile.go (about) 1 // Copyright 2023 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package filelock 6 7 import ( 8 "io" 9 "io/fs" 10 "os" 11 12 os_ "github.com/searKing/golang/go/os" 13 ) 14 15 // OpenFile is like os.OpenFile, but returns a locked file. 16 // If flag includes os.O_WRONLY or os.O_RDWR, the file is write-locked; 17 // otherwise, it is read-locked. 18 func OpenFile(name string, flag int, perm fs.FileMode) (*LockedFile[*os.File], error) { 19 file, err := openFile(name, flag, perm) 20 if err != nil { 21 return nil, err 22 } 23 return NewLockedFile(file) 24 } 25 26 // Open is like os.Open, but returns a read-locked file. 27 func Open(name string) (*LockedFile[*os.File], error) { 28 return OpenFile(name, os.O_RDONLY, 0) 29 } 30 31 // Create is like os.Create, but returns a write-locked file. 32 func Create(name string) (*LockedFile[*os.File], error) { 33 return OpenFile(name, os_.DefaultFlagCreateTruncate, os_.DefaultPermissionFile) 34 } 35 36 // Edit creates the named file with mode 0666 (before umask), 37 // but does not truncate existing contents. 38 // 39 // If Edit succeeds, methods on the returned File can be used for I/O. 40 // The associated file descriptor has mode O_RDWR and the file is write-locked. 41 func Edit(name string) (*LockedFile[*os.File], error) { 42 return OpenFile(name, os_.DefaultFlagCreateIfNotExist, os_.DefaultPermissionFile) 43 } 44 45 // Read opens the named file with a read-lock and returns its contents. 46 func Read(name string) ([]byte, error) { 47 f, err := Open(name) 48 if err != nil { 49 return nil, err 50 } 51 defer f.Close() 52 53 return io.ReadAll(f.File) 54 } 55 56 // Write opens the named file (creating it with the given permissions if needed), 57 // then write-locks it and overwrites it with the given content. 58 func Write(name string, content io.Reader, perm fs.FileMode) (err error) { 59 f, err := OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) 60 if err != nil { 61 return err 62 } 63 64 _, err = io.Copy(f.File, content) 65 if closeErr := f.Close(); err == nil { 66 err = closeErr 67 } 68 return err 69 } 70 71 // Transform invokes t with the result of reading the named file, with its lock 72 // still held. 73 // 74 // If t returns a nil error, Transform then writes the returned contents back to 75 // the file, making a best effort to preserve existing contents on error. 76 // 77 // t must not modify the slice passed to it. 78 func Transform(name string, t func([]byte) ([]byte, error)) (err error) { 79 f, err := Edit(name) 80 if err != nil { 81 return err 82 } 83 defer f.Close() 84 85 old, err := io.ReadAll(f.File) 86 if err != nil { 87 return err 88 } 89 90 new, err := t(old) 91 if err != nil { 92 return err 93 } 94 95 if len(new) > len(old) { 96 // The overall file size is increasing, so write the tail first: if we're 97 // about to run out of space on the disk, we would rather detect that 98 // failure before we have overwritten the original contents. 99 if _, err := f.File.WriteAt(new[len(old):], int64(len(old))); err != nil { 100 // Make a best effort to remove the incomplete tail. 101 f.File.Truncate(int64(len(old))) 102 return err 103 } 104 } 105 106 // We're about to overwrite the old contents. In case of failure, make a best 107 // effort to roll back before we close the file. 108 defer func() { 109 if err != nil { 110 if _, err := f.File.WriteAt(old, 0); err == nil { 111 f.File.Truncate(int64(len(old))) 112 } 113 } 114 }() 115 116 if len(new) >= len(old) { 117 if _, err := f.File.WriteAt(new[:len(old)], 0); err != nil { 118 return err 119 } 120 } else { 121 if _, err := f.File.WriteAt(new, 0); err != nil { 122 return err 123 } 124 // The overall file size is decreasing, so shrink the file to its final size 125 // after writing. We do this after writing (instead of before) so that if 126 // the write fails, enough filesystem space will likely still be reserved 127 // to contain the previous contents. 128 if err := f.File.Truncate(int64(len(new))); err != nil { 129 return err 130 } 131 } 132 133 return nil 134 }