storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/safe/safe.go (about) 1 /* 2 * MinIO Cloud Storage (C) 2015-2016 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // NOTE - Rename() not guaranteed to be safe on all filesystems which are not fully POSIX compatible 18 19 package safe 20 21 import ( 22 "errors" 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 ) 27 28 // File represents safe file descriptor. 29 type File struct { 30 name string 31 tmpfile *os.File 32 closed bool 33 aborted bool 34 } 35 36 // Write writes len(b) bytes to the temporary File. In case of error, the temporary file is removed. 37 func (file *File) Write(b []byte) (n int, err error) { 38 if file.closed { 39 err = errors.New("write on closed file") 40 return 41 } 42 if file.aborted { 43 err = errors.New("write on aborted file") 44 return 45 } 46 47 defer func() { 48 if err != nil { 49 os.Remove(file.tmpfile.Name()) 50 file.aborted = true 51 } 52 }() 53 54 n, err = file.tmpfile.Write(b) 55 return 56 } 57 58 // Close closes the temporary File and renames to the named file. In case of error, the temporary file is removed. 59 func (file *File) Close() (err error) { 60 defer func() { 61 if err != nil { 62 os.Remove(file.tmpfile.Name()) 63 file.aborted = true 64 } 65 }() 66 67 if file.closed { 68 err = errors.New("close on closed file") 69 return 70 } 71 if file.aborted { 72 err = errors.New("close on aborted file") 73 return 74 } 75 76 if err = file.tmpfile.Close(); err != nil { 77 return 78 } 79 80 err = os.Rename(file.tmpfile.Name(), file.name) 81 82 file.closed = true 83 return 84 } 85 86 // Abort aborts the temporary File by closing and removing the temporary file. 87 func (file *File) Abort() (err error) { 88 if file.closed { 89 err = errors.New("abort on closed file") 90 return 91 } 92 if file.aborted { 93 err = errors.New("abort on aborted file") 94 return 95 } 96 97 file.tmpfile.Close() 98 err = os.Remove(file.tmpfile.Name()) 99 file.aborted = true 100 return 101 } 102 103 // CreateFile creates the named file safely from unique temporary file. 104 // The temporary file is renamed to the named file upon successful close 105 // to safeguard intermediate state in the named file. The temporary file 106 // is created in the name of the named file with suffixed unique number 107 // and prefixed "$tmpfile" string. While creating the temporary file, 108 // missing parent directories are also created. The temporary file is 109 // removed if case of any intermediate failure. Not removed temporary 110 // files can be cleaned up by identifying them using "$tmpfile" prefix 111 // string. 112 func CreateFile(name string) (*File, error) { 113 // ioutil.TempFile() fails if parent directory is missing. 114 // Create parent directory to avoid such error. 115 dname := filepath.Dir(name) 116 if err := os.MkdirAll(dname, 0700); err != nil { 117 return nil, err 118 } 119 120 fname := filepath.Base(name) 121 tmpfile, err := ioutil.TempFile(dname, "$tmpfile."+fname+".") 122 if err != nil { 123 return nil, err 124 } 125 126 if err = os.Chmod(tmpfile.Name(), 0600); err != nil { 127 if rerr := os.Remove(tmpfile.Name()); rerr != nil { 128 err = rerr 129 } 130 return nil, err 131 } 132 133 return &File{name: name, tmpfile: tmpfile}, nil 134 }