1 // Copyright 2018 The Go Authors. 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 lockedfile creates and manipulates files whose contents should only 6 // change atomically. 7 package lockedfile 8 9 import ( 10 "fmt" 11 "io" 12 "io/ioutil" 13 "os" 14 "runtime" 15 ) 16 17 // A File is a locked *os.File. 18 // 19 // Closing the file releases the lock. 20 // 21 // If the program exits while a file is locked, the operating system releases 22 // the lock but may not do so promptly: callers must ensure that all locked 23 // files are closed before exiting. 24 type File struct { 25 osFile 26 closed bool 27 } 28 29 // osFile embeds a *os.File while keeping the pointer itself unexported. 30 // (When we close a File, it must be the same file descriptor that we opened!) 31 type osFile struct { 32 *os.File 33 } 34 35 // OpenFile is like os.OpenFile, but returns a locked file. 36 // If flag includes os.O_WRONLY or os.O_RDWR, the file is write-locked; 37 // otherwise, it is read-locked. 38 func OpenFile(name string, flag int, perm os.FileMode) (*File, error) { 39 var ( 40 f = new(File) 41 err error 42 ) 43 f.osFile.File, err = openFile(name, flag, perm) 44 if err != nil { 45 return nil, err 46 } 47 48 // Although the operating system will drop locks for open files when the go 49 // command exits, we want to hold locks for as little time as possible, and we 50 // especially don't want to leave a file locked after we're done with it. Our 51 // Close method is what releases the locks, so use a finalizer to report 52 // missing Close calls on a best-effort basis. 53 runtime.SetFinalizer(f, func(f *File) { 54 panic(fmt.Sprintf("lockedfile.File %s became unreachable without a call to Close", f.Name())) 55 }) 56 57 return f, nil 58 } 59 60 // Open is like os.Open, but returns a read-locked file. 61 func Open(name string) (*File, error) { 62 return OpenFile(name, os.O_RDONLY, 0) 63 } 64 65 // Create is like os.Create, but returns a write-locked file. 66 func Create(name string) (*File, error) { 67 return OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 68 } 69 70 // Edit creates the named file with mode 0666 (before umask), 71 // but does not truncate existing contents. 72 // 73 // If Edit succeeds, methods on the returned File can be used for I/O. 74 // The associated file descriptor has mode O_RDWR and the file is write-locked. 75 func Edit(name string) (*File, error) { 76 return OpenFile(name, os.O_RDWR|os.O_CREATE, 0666) 77 } 78 79 // Close unlocks and closes the underlying file. 80 // 81 // Close may be called multiple times; all calls after the first will return a 82 // non-nil error. 83 func (f *File) Close() error { 84 if f.closed { 85 return &os.PathError{ 86 Op: "close", 87 Path: f.Name(), 88 Err: os.ErrClosed, 89 } 90 } 91 f.closed = true 92 93 err := closeFile(f.osFile.File) 94 runtime.SetFinalizer(f, nil) 95 return err 96 } 97 98 // Read opens the named file with a read-lock and returns its contents. 99 func Read(name string) ([]byte, error) { 100 f, err := Open(name) 101 if err != nil { 102 return nil, err 103 } 104 defer f.Close() 105 106 return ioutil.ReadAll(f) 107 } 108 109 // Write opens the named file (creating it with the given permissions if needed), 110 // then write-locks it and overwrites it with the given content. 111 func Write(name string, content io.Reader, perm os.FileMode) (err error) { 112 f, err := OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) 113 if err != nil { 114 return err 115 } 116 117 _, err = io.Copy(f, content) 118 if closeErr := f.Close(); err == nil { 119 err = closeErr 120 } 121 return err 122 } 123