github.com/lusis/distribution@v2.0.1+incompatible/registry/storage/filewriter.go (about) 1 package storage 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io" 8 "os" 9 10 storagedriver "github.com/docker/distribution/registry/storage/driver" 11 ) 12 13 const ( 14 fileWriterBufferSize = 5 << 20 15 ) 16 17 // fileWriter implements a remote file writer backed by a storage driver. 18 type fileWriter struct { 19 driver storagedriver.StorageDriver 20 21 // identifying fields 22 path string 23 24 // mutable fields 25 size int64 // size of the file, aka the current end 26 offset int64 // offset is the current write offset 27 err error // terminal error, if set, reader is closed 28 } 29 30 type bufferedFileWriter struct { 31 fileWriter 32 bw *bufio.Writer 33 } 34 35 // fileWriterInterface makes the desired io compliant interface that the 36 // filewriter should implement. 37 type fileWriterInterface interface { 38 io.WriteSeeker 39 io.WriterAt 40 io.ReaderFrom 41 io.Closer 42 } 43 44 var _ fileWriterInterface = &fileWriter{} 45 46 // newFileWriter returns a prepared fileWriter for the driver and path. This 47 // could be considered similar to an "open" call on a regular filesystem. 48 func newFileWriter(driver storagedriver.StorageDriver, path string) (*bufferedFileWriter, error) { 49 fw := fileWriter{ 50 driver: driver, 51 path: path, 52 } 53 54 if fi, err := driver.Stat(path); err != nil { 55 switch err := err.(type) { 56 case storagedriver.PathNotFoundError: 57 // ignore, offset is zero 58 default: 59 return nil, err 60 } 61 } else { 62 if fi.IsDir() { 63 return nil, fmt.Errorf("cannot write to a directory") 64 } 65 66 fw.size = fi.Size() 67 } 68 69 buffered := bufferedFileWriter{ 70 fileWriter: fw, 71 } 72 buffered.bw = bufio.NewWriterSize(&buffered.fileWriter, fileWriterBufferSize) 73 74 return &buffered, nil 75 } 76 77 // wraps the fileWriter.Write method to buffer small writes 78 func (bfw *bufferedFileWriter) Write(p []byte) (int, error) { 79 return bfw.bw.Write(p) 80 } 81 82 // wraps fileWriter.Close to ensure the buffer is flushed 83 // before we close the writer. 84 func (bfw *bufferedFileWriter) Close() (err error) { 85 if err = bfw.Flush(); err != nil { 86 return err 87 } 88 err = bfw.fileWriter.Close() 89 return err 90 } 91 92 // wraps fileWriter.Seek to ensure offset is handled 93 // correctly in respect to pending data in the buffer 94 func (bfw *bufferedFileWriter) Seek(offset int64, whence int) (int64, error) { 95 if err := bfw.Flush(); err != nil { 96 return 0, err 97 } 98 return bfw.fileWriter.Seek(offset, whence) 99 } 100 101 // wraps bufio.Writer.Flush to allow intermediate flushes 102 // of the bufferedFileWriter 103 func (bfw *bufferedFileWriter) Flush() error { 104 return bfw.bw.Flush() 105 } 106 107 // Write writes the buffer p at the current write offset. 108 func (fw *fileWriter) Write(p []byte) (n int, err error) { 109 nn, err := fw.readFromAt(bytes.NewReader(p), -1) 110 return int(nn), err 111 } 112 113 // WriteAt writes p at the specified offset. The underlying offset does not 114 // change. 115 func (fw *fileWriter) WriteAt(p []byte, offset int64) (n int, err error) { 116 nn, err := fw.readFromAt(bytes.NewReader(p), offset) 117 return int(nn), err 118 } 119 120 // ReadFrom reads reader r until io.EOF writing the contents at the current 121 // offset. 122 func (fw *fileWriter) ReadFrom(r io.Reader) (n int64, err error) { 123 return fw.readFromAt(r, -1) 124 } 125 126 // Seek moves the write position do the requested offest based on the whence 127 // argument, which can be os.SEEK_CUR, os.SEEK_END, or os.SEEK_SET. 128 func (fw *fileWriter) Seek(offset int64, whence int) (int64, error) { 129 if fw.err != nil { 130 return 0, fw.err 131 } 132 133 var err error 134 newOffset := fw.offset 135 136 switch whence { 137 case os.SEEK_CUR: 138 newOffset += int64(offset) 139 case os.SEEK_END: 140 newOffset = fw.size + int64(offset) 141 case os.SEEK_SET: 142 newOffset = int64(offset) 143 } 144 145 if newOffset < 0 { 146 err = fmt.Errorf("cannot seek to negative position") 147 } else { 148 // No problems, set the offset. 149 fw.offset = newOffset 150 } 151 152 return fw.offset, err 153 } 154 155 // Close closes the fileWriter for writing. 156 // Calling it once is valid and correct and it will 157 // return a nil error. Calling it subsequent times will 158 // detect that fw.err has been set and will return the error. 159 func (fw *fileWriter) Close() error { 160 if fw.err != nil { 161 return fw.err 162 } 163 164 fw.err = fmt.Errorf("filewriter@%v: closed", fw.path) 165 166 return nil 167 } 168 169 // readFromAt writes to fw from r at the specified offset. If offset is less 170 // than zero, the value of fw.offset is used and updated after the operation. 171 func (fw *fileWriter) readFromAt(r io.Reader, offset int64) (n int64, err error) { 172 if fw.err != nil { 173 return 0, fw.err 174 } 175 176 var updateOffset bool 177 if offset < 0 { 178 offset = fw.offset 179 updateOffset = true 180 } 181 182 nn, err := fw.driver.WriteStream(fw.path, offset, r) 183 184 if updateOffset { 185 // We should forward the offset, whether or not there was an error. 186 // Basically, we keep the filewriter in sync with the reader's head. If an 187 // error is encountered, the whole thing should be retried but we proceed 188 // from an expected offset, even if the data didn't make it to the 189 // backend. 190 fw.offset += nn 191 192 if fw.offset > fw.size { 193 fw.size = fw.offset 194 } 195 } 196 197 return nn, err 198 }