github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/builder/remotecontext/tarsum.go (about) 1 package remotecontext // import "github.com/demonoid81/moby/builder/remotecontext" 2 3 import ( 4 "os" 5 "sync" 6 7 "github.com/demonoid81/moby/pkg/containerfs" 8 iradix "github.com/hashicorp/go-immutable-radix" 9 digest "github.com/opencontainers/go-digest" 10 "github.com/pkg/errors" 11 "github.com/tonistiigi/fsutil" 12 ) 13 14 type hashed interface { 15 Digest() digest.Digest 16 } 17 18 // CachableSource is a source that contains cache records for its contents 19 type CachableSource struct { 20 mu sync.Mutex 21 root containerfs.ContainerFS 22 tree *iradix.Tree 23 txn *iradix.Txn 24 } 25 26 // NewCachableSource creates new CachableSource 27 func NewCachableSource(root string) *CachableSource { 28 ts := &CachableSource{ 29 tree: iradix.New(), 30 root: containerfs.NewLocalContainerFS(root), 31 } 32 return ts 33 } 34 35 // MarshalBinary marshals current cache information to a byte array 36 func (cs *CachableSource) MarshalBinary() ([]byte, error) { 37 b := TarsumBackup{Hashes: make(map[string]string)} 38 root := cs.getRoot() 39 root.Walk(func(k []byte, v interface{}) bool { 40 b.Hashes[string(k)] = v.(*fileInfo).sum 41 return false 42 }) 43 return b.Marshal() 44 } 45 46 // UnmarshalBinary decodes cache information for presented byte array 47 func (cs *CachableSource) UnmarshalBinary(data []byte) error { 48 var b TarsumBackup 49 if err := b.Unmarshal(data); err != nil { 50 return err 51 } 52 txn := iradix.New().Txn() 53 for p, v := range b.Hashes { 54 txn.Insert([]byte(p), &fileInfo{sum: v}) 55 } 56 cs.mu.Lock() 57 defer cs.mu.Unlock() 58 cs.tree = txn.Commit() 59 return nil 60 } 61 62 // Scan rescans the cache information from the file system 63 func (cs *CachableSource) Scan() error { 64 lc, err := NewLazySource(cs.root) 65 if err != nil { 66 return err 67 } 68 txn := iradix.New().Txn() 69 err = cs.root.Walk(cs.root.Path(), func(path string, info os.FileInfo, err error) error { 70 if err != nil { 71 return errors.Wrapf(err, "failed to walk %s", path) 72 } 73 rel, err := Rel(cs.root, path) 74 if err != nil { 75 return err 76 } 77 h, err := lc.Hash(rel) 78 if err != nil { 79 return err 80 } 81 txn.Insert([]byte(rel), &fileInfo{sum: h}) 82 return nil 83 }) 84 if err != nil { 85 return err 86 } 87 cs.mu.Lock() 88 defer cs.mu.Unlock() 89 cs.tree = txn.Commit() 90 return nil 91 } 92 93 // HandleChange notifies the source about a modification operation 94 func (cs *CachableSource) HandleChange(kind fsutil.ChangeKind, p string, fi os.FileInfo, err error) (retErr error) { 95 cs.mu.Lock() 96 if cs.txn == nil { 97 cs.txn = cs.tree.Txn() 98 } 99 if kind == fsutil.ChangeKindDelete { 100 cs.txn.Delete([]byte(p)) 101 cs.mu.Unlock() 102 return 103 } 104 105 h, ok := fi.(hashed) 106 if !ok { 107 cs.mu.Unlock() 108 return errors.Errorf("invalid fileinfo: %s", p) 109 } 110 111 hfi := &fileInfo{ 112 sum: h.Digest().Hex(), 113 } 114 cs.txn.Insert([]byte(p), hfi) 115 cs.mu.Unlock() 116 return nil 117 } 118 119 func (cs *CachableSource) getRoot() *iradix.Node { 120 cs.mu.Lock() 121 if cs.txn != nil { 122 cs.tree = cs.txn.Commit() 123 cs.txn = nil 124 } 125 t := cs.tree 126 cs.mu.Unlock() 127 return t.Root() 128 } 129 130 // Close closes the source 131 func (cs *CachableSource) Close() error { 132 return nil 133 } 134 135 // Hash returns a hash for a single file in the source 136 func (cs *CachableSource) Hash(path string) (string, error) { 137 n := cs.getRoot() 138 // TODO: check this for symlinks 139 v, ok := n.Get([]byte(path)) 140 if !ok { 141 return path, nil 142 } 143 return v.(*fileInfo).sum, nil 144 } 145 146 // Root returns a root directory for the source 147 func (cs *CachableSource) Root() containerfs.ContainerFS { 148 return cs.root 149 } 150 151 type fileInfo struct { 152 sum string 153 } 154 155 func (fi *fileInfo) Hash() string { 156 return fi.sum 157 }