github.com/demonoid81/containerd@v1.3.4/content/local/writer.go (about) 1 /* 2 Copyright The containerd Authors. 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 package local 18 19 import ( 20 "context" 21 "io" 22 "os" 23 "path/filepath" 24 "runtime" 25 "time" 26 27 "github.com/containerd/containerd/content" 28 "github.com/containerd/containerd/errdefs" 29 "github.com/containerd/containerd/log" 30 "github.com/opencontainers/go-digest" 31 "github.com/pkg/errors" 32 ) 33 34 // writer represents a write transaction against the blob store. 35 type writer struct { 36 s *store 37 fp *os.File // opened data file 38 path string // path to writer dir 39 ref string // ref key 40 offset int64 41 total int64 42 digester digest.Digester 43 startedAt time.Time 44 updatedAt time.Time 45 } 46 47 func (w *writer) Status() (content.Status, error) { 48 return content.Status{ 49 Ref: w.ref, 50 Offset: w.offset, 51 Total: w.total, 52 StartedAt: w.startedAt, 53 UpdatedAt: w.updatedAt, 54 }, nil 55 } 56 57 // Digest returns the current digest of the content, up to the current write. 58 // 59 // Cannot be called concurrently with `Write`. 60 func (w *writer) Digest() digest.Digest { 61 return w.digester.Digest() 62 } 63 64 // Write p to the transaction. 65 // 66 // Note that writes are unbuffered to the backing file. When writing, it is 67 // recommended to wrap in a bufio.Writer or, preferably, use io.CopyBuffer. 68 func (w *writer) Write(p []byte) (n int, err error) { 69 n, err = w.fp.Write(p) 70 w.digester.Hash().Write(p[:n]) 71 w.offset += int64(len(p)) 72 w.updatedAt = time.Now() 73 return n, err 74 } 75 76 func (w *writer) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error { 77 // Ensure even on error the writer is fully closed 78 defer unlock(w.ref) 79 80 var base content.Info 81 for _, opt := range opts { 82 if err := opt(&base); err != nil { 83 return err 84 } 85 } 86 87 fp := w.fp 88 w.fp = nil 89 90 if fp == nil { 91 return errors.Wrap(errdefs.ErrFailedPrecondition, "cannot commit on closed writer") 92 } 93 94 if err := fp.Sync(); err != nil { 95 fp.Close() 96 return errors.Wrap(err, "sync failed") 97 } 98 99 fi, err := fp.Stat() 100 closeErr := fp.Close() 101 if err != nil { 102 return errors.Wrap(err, "stat on ingest file failed") 103 } 104 if closeErr != nil { 105 return errors.Wrap(err, "failed to close ingest file") 106 } 107 108 if size > 0 && size != fi.Size() { 109 return errors.Wrapf(errdefs.ErrFailedPrecondition, "unexpected commit size %d, expected %d", fi.Size(), size) 110 } 111 112 dgst := w.digester.Digest() 113 if expected != "" && expected != dgst { 114 return errors.Wrapf(errdefs.ErrFailedPrecondition, "unexpected commit digest %s, expected %s", dgst, expected) 115 } 116 117 var ( 118 ingest = filepath.Join(w.path, "data") 119 target = w.s.blobPath(dgst) 120 ) 121 122 // make sure parent directories of blob exist 123 if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { 124 return err 125 } 126 127 if _, err := os.Stat(target); err == nil { 128 // collision with the target file! 129 if err := os.RemoveAll(w.path); err != nil { 130 log.G(ctx).WithField("ref", w.ref).WithField("path", w.path).Errorf("failed to remove ingest directory") 131 } 132 return errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", dgst) 133 } 134 135 if err := os.Rename(ingest, target); err != nil { 136 return err 137 } 138 139 // Ingest has now been made available in the content store, attempt to complete 140 // setting metadata but errors should only be logged and not returned since 141 // the content store cannot be cleanly rolled back. 142 143 commitTime := time.Now() 144 if err := os.Chtimes(target, commitTime, commitTime); err != nil { 145 log.G(ctx).WithField("digest", dgst).Errorf("failed to change file time to commit time") 146 } 147 148 // clean up!! 149 if err := os.RemoveAll(w.path); err != nil { 150 log.G(ctx).WithField("ref", w.ref).WithField("path", w.path).Errorf("failed to remove ingest directory") 151 } 152 153 if w.s.ls != nil && base.Labels != nil { 154 if err := w.s.ls.Set(dgst, base.Labels); err != nil { 155 log.G(ctx).WithField("digest", dgst).Errorf("failed to set labels") 156 } 157 } 158 159 // change to readonly, more important for read, but provides _some_ 160 // protection from this point on. We use the existing perms with a mask 161 // only allowing reads honoring the umask on creation. 162 // 163 // This removes write and exec, only allowing read per the creation umask. 164 // 165 // NOTE: Windows does not support this operation 166 if runtime.GOOS != "windows" { 167 if err := os.Chmod(target, (fi.Mode()&os.ModePerm)&^0333); err != nil { 168 log.G(ctx).WithField("ref", w.ref).Errorf("failed to make readonly") 169 } 170 } 171 172 return nil 173 } 174 175 // Close the writer, flushing any unwritten data and leaving the progress in 176 // tact. 177 // 178 // If one needs to resume the transaction, a new writer can be obtained from 179 // `Ingester.Writer` using the same key. The write can then be continued 180 // from it was left off. 181 // 182 // To abandon a transaction completely, first call close then `IngestManager.Abort` to 183 // clean up the associated resources. 184 func (w *writer) Close() (err error) { 185 if w.fp != nil { 186 w.fp.Sync() 187 err = w.fp.Close() 188 writeTimestampFile(filepath.Join(w.path, "updatedat"), w.updatedAt) 189 w.fp = nil 190 unlock(w.ref) 191 return 192 } 193 194 return nil 195 } 196 197 func (w *writer) Truncate(size int64) error { 198 if size != 0 { 199 return errors.New("Truncate: unsupported size") 200 } 201 w.offset = 0 202 w.digester.Hash().Reset() 203 if _, err := w.fp.Seek(0, io.SeekStart); err != nil { 204 return err 205 } 206 return w.fp.Truncate(0) 207 }