github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/snapshots/native/native.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 native 18 19 import ( 20 "context" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 25 "github.com/containerd/containerd/log" 26 "github.com/containerd/containerd/mount" 27 "github.com/containerd/containerd/snapshots" 28 "github.com/containerd/containerd/snapshots/storage" 29 30 "github.com/containerd/continuity/fs" 31 "github.com/pkg/errors" 32 ) 33 34 type snapshotter struct { 35 root string 36 ms *storage.MetaStore 37 } 38 39 // NewSnapshotter returns a Snapshotter which copies layers on the underlying 40 // file system. A metadata file is stored under the root. 41 func NewSnapshotter(root string) (snapshots.Snapshotter, error) { 42 if err := os.MkdirAll(root, 0700); err != nil { 43 return nil, err 44 } 45 ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db")) 46 if err != nil { 47 return nil, err 48 } 49 50 if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) { 51 return nil, err 52 } 53 54 return &snapshotter{ 55 root: root, 56 ms: ms, 57 }, nil 58 } 59 60 // Stat returns the info for an active or committed snapshot by name or 61 // key. 62 // 63 // Should be used for parent resolution, existence checks and to discern 64 // the kind of snapshot. 65 func (o *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) { 66 ctx, t, err := o.ms.TransactionContext(ctx, false) 67 if err != nil { 68 return snapshots.Info{}, err 69 } 70 defer t.Rollback() 71 _, info, _, err := storage.GetInfo(ctx, key) 72 if err != nil { 73 return snapshots.Info{}, err 74 } 75 76 return info, nil 77 } 78 79 func (o *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) { 80 ctx, t, err := o.ms.TransactionContext(ctx, true) 81 if err != nil { 82 return snapshots.Info{}, err 83 } 84 85 info, err = storage.UpdateInfo(ctx, info, fieldpaths...) 86 if err != nil { 87 t.Rollback() 88 return snapshots.Info{}, err 89 } 90 91 if err := t.Commit(); err != nil { 92 return snapshots.Info{}, err 93 } 94 95 return info, nil 96 } 97 98 func (o *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) { 99 ctx, t, err := o.ms.TransactionContext(ctx, false) 100 if err != nil { 101 return snapshots.Usage{}, err 102 } 103 defer t.Rollback() 104 105 id, info, usage, err := storage.GetInfo(ctx, key) 106 if err != nil { 107 return snapshots.Usage{}, err 108 } 109 110 if info.Kind == snapshots.KindActive { 111 du, err := fs.DiskUsage(ctx, o.getSnapshotDir(id)) 112 if err != nil { 113 return snapshots.Usage{}, err 114 } 115 usage = snapshots.Usage(du) 116 } 117 118 return usage, nil 119 } 120 121 func (o *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { 122 return o.createSnapshot(ctx, snapshots.KindActive, key, parent, opts) 123 } 124 125 func (o *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { 126 return o.createSnapshot(ctx, snapshots.KindView, key, parent, opts) 127 } 128 129 // Mounts returns the mounts for the transaction identified by key. Can be 130 // called on an read-write or readonly transaction. 131 // 132 // This can be used to recover mounts after calling View or Prepare. 133 func (o *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) { 134 ctx, t, err := o.ms.TransactionContext(ctx, false) 135 if err != nil { 136 return nil, err 137 } 138 s, err := storage.GetSnapshot(ctx, key) 139 t.Rollback() 140 if err != nil { 141 return nil, errors.Wrap(err, "failed to get snapshot mount") 142 } 143 return o.mounts(s), nil 144 } 145 146 func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error { 147 ctx, t, err := o.ms.TransactionContext(ctx, true) 148 if err != nil { 149 return err 150 } 151 152 id, _, _, err := storage.GetInfo(ctx, key) 153 if err != nil { 154 return err 155 } 156 157 usage, err := fs.DiskUsage(ctx, o.getSnapshotDir(id)) 158 if err != nil { 159 return err 160 } 161 162 if _, err := storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil { 163 if rerr := t.Rollback(); rerr != nil { 164 log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") 165 } 166 return errors.Wrap(err, "failed to commit snapshot") 167 } 168 return t.Commit() 169 } 170 171 // Remove abandons the transaction identified by key. All resources 172 // associated with the key will be removed. 173 func (o *snapshotter) Remove(ctx context.Context, key string) (err error) { 174 ctx, t, err := o.ms.TransactionContext(ctx, true) 175 if err != nil { 176 return err 177 } 178 defer func() { 179 if err != nil && t != nil { 180 if rerr := t.Rollback(); rerr != nil { 181 log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") 182 } 183 } 184 }() 185 186 id, _, err := storage.Remove(ctx, key) 187 if err != nil { 188 return errors.Wrap(err, "failed to remove") 189 } 190 191 path := o.getSnapshotDir(id) 192 renamed := filepath.Join(o.root, "snapshots", "rm-"+id) 193 if err := os.Rename(path, renamed); err != nil { 194 if !os.IsNotExist(err) { 195 return errors.Wrap(err, "failed to rename") 196 } 197 renamed = "" 198 } 199 200 err = t.Commit() 201 t = nil 202 if err != nil { 203 if renamed != "" { 204 if err1 := os.Rename(renamed, path); err1 != nil { 205 // May cause inconsistent data on disk 206 log.G(ctx).WithError(err1).WithField("path", renamed).Errorf("failed to rename after failed commit") 207 } 208 } 209 return errors.Wrap(err, "failed to commit") 210 } 211 if renamed != "" { 212 if err := os.RemoveAll(renamed); err != nil { 213 // Must be cleaned up, any "rm-*" could be removed if no active transactions 214 log.G(ctx).WithError(err).WithField("path", renamed).Warnf("failed to remove root filesystem") 215 } 216 } 217 218 return nil 219 } 220 221 // Walk the committed snapshots. 222 func (o *snapshotter) Walk(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error { 223 ctx, t, err := o.ms.TransactionContext(ctx, false) 224 if err != nil { 225 return err 226 } 227 defer t.Rollback() 228 return storage.WalkInfo(ctx, fn, fs...) 229 } 230 231 func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) (_ []mount.Mount, err error) { 232 var ( 233 path, td string 234 ) 235 236 if kind == snapshots.KindActive || parent == "" { 237 td, err = ioutil.TempDir(filepath.Join(o.root, "snapshots"), "new-") 238 if err != nil { 239 return nil, errors.Wrap(err, "failed to create temp dir") 240 } 241 if err := os.Chmod(td, 0755); err != nil { 242 return nil, errors.Wrapf(err, "failed to chmod %s to 0755", td) 243 } 244 defer func() { 245 if err != nil { 246 if td != "" { 247 if err1 := os.RemoveAll(td); err1 != nil { 248 err = errors.Wrapf(err, "remove failed: %v", err1) 249 } 250 } 251 if path != "" { 252 if err1 := os.RemoveAll(path); err1 != nil { 253 err = errors.Wrapf(err, "failed to remove path: %v", err1) 254 } 255 } 256 } 257 }() 258 } 259 260 ctx, t, err := o.ms.TransactionContext(ctx, true) 261 if err != nil { 262 return nil, err 263 } 264 265 s, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...) 266 if err != nil { 267 if rerr := t.Rollback(); rerr != nil { 268 log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") 269 } 270 return nil, errors.Wrap(err, "failed to create snapshot") 271 } 272 273 if td != "" { 274 if len(s.ParentIDs) > 0 { 275 parent := o.getSnapshotDir(s.ParentIDs[0]) 276 xattrErrorHandler := func(dst, src, xattrKey string, copyErr error) error { 277 // security.* xattr cannot be copied in most cases (moby/buildkit#1189) 278 log.G(ctx).WithError(copyErr).Debugf("failed to copy xattr %q", xattrKey) 279 return nil 280 } 281 copyDirOpts := []fs.CopyDirOpt{ 282 fs.WithXAttrErrorHandler(xattrErrorHandler), 283 } 284 if err := fs.CopyDir(td, parent, copyDirOpts...); err != nil { 285 return nil, errors.Wrap(err, "copying of parent failed") 286 } 287 } 288 289 path = o.getSnapshotDir(s.ID) 290 if err := os.Rename(td, path); err != nil { 291 if rerr := t.Rollback(); rerr != nil { 292 log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") 293 } 294 return nil, errors.Wrap(err, "failed to rename") 295 } 296 td = "" 297 } 298 299 if err := t.Commit(); err != nil { 300 return nil, errors.Wrap(err, "commit failed") 301 } 302 303 return o.mounts(s), nil 304 } 305 306 func (o *snapshotter) getSnapshotDir(id string) string { 307 return filepath.Join(o.root, "snapshots", id) 308 } 309 310 func (o *snapshotter) mounts(s storage.Snapshot) []mount.Mount { 311 var ( 312 roFlag string 313 source string 314 ) 315 316 if s.Kind == snapshots.KindView { 317 roFlag = "ro" 318 } else { 319 roFlag = "rw" 320 } 321 322 if len(s.ParentIDs) == 0 || s.Kind == snapshots.KindActive { 323 source = o.getSnapshotDir(s.ID) 324 } else { 325 source = o.getSnapshotDir(s.ParentIDs[0]) 326 } 327 328 return []mount.Mount{ 329 { 330 Source: source, 331 Type: "bind", 332 Options: []string{ 333 roFlag, 334 "rbind", 335 }, 336 }, 337 } 338 } 339 340 // Close closes the snapshotter 341 func (o *snapshotter) Close() error { 342 return o.ms.Close() 343 }