github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/installer/objects.go (about) 1 // +build linux 2 3 package main 4 5 import ( 6 "crypto/sha512" 7 "errors" 8 "fmt" 9 "io" 10 "os" 11 "path/filepath" 12 "syscall" 13 "time" 14 15 "github.com/Cloud-Foundations/Dominator/lib/format" 16 "github.com/Cloud-Foundations/Dominator/lib/fsutil" 17 "github.com/Cloud-Foundations/Dominator/lib/hash" 18 "github.com/Cloud-Foundations/Dominator/lib/log" 19 "github.com/Cloud-Foundations/Dominator/lib/objectcache" 20 "github.com/Cloud-Foundations/Dominator/lib/objectserver" 21 "github.com/Cloud-Foundations/Dominator/lib/wsyscall" 22 ) 23 24 type objectsCache struct { 25 bytesScanned uint64 26 objects map[hash.Hash]uint64 27 } 28 29 type objectsReader struct { 30 cache *objectsCache 31 hashes []hash.Hash 32 } 33 34 func hashFile(filename string) (hash.Hash, uint64, error) { 35 file, err := os.Open(filename) 36 if err != nil { 37 return hash.Hash{}, 0, err 38 } 39 defer file.Close() 40 hasher := sha512.New() 41 nCopied, err := io.Copy(hasher, file) 42 if err != nil { 43 return hash.Hash{}, 0, err 44 } 45 var hashVal hash.Hash 46 copy(hashVal[:], hasher.Sum(nil)) 47 return hashVal, uint64(nCopied), nil 48 } 49 50 func (cache *objectsCache) computeMissing( 51 requiredObjects map[hash.Hash]uint64) ( 52 map[hash.Hash]uint64, uint64, uint64) { 53 var requiredBytes, presentBytes uint64 54 missingObjects := make(map[hash.Hash]uint64, len(requiredObjects)) 55 for hashVal, requiredSize := range requiredObjects { 56 requiredBytes += requiredSize 57 if size, ok := cache.objects[hashVal]; ok { 58 presentBytes += size 59 } else { 60 missingObjects[hashVal] = requiredSize 61 } 62 } 63 return missingObjects, requiredBytes, presentBytes 64 } 65 66 func createObjectsCache(requiredObjects map[hash.Hash]uint64, 67 objGetter objectserver.ObjectsGetter, rootDevice string, 68 logger log.DebugLogger) (*objectsCache, error) { 69 cache := &objectsCache{objects: make(map[hash.Hash]uint64)} 70 if fi, err := os.Stat(*objectsDirectory); err != nil { 71 if !os.IsNotExist(err) { 72 return nil, err 73 } 74 logger.Debugln(0, "scanning root") 75 cache.bytesScanned = 0 76 startTime := time.Now() 77 if err := cache.scanRoot(requiredObjects); err != nil { 78 return nil, err 79 } 80 duration := time.Since(startTime) 81 logger.Debugf(0, "scanned root %s in %s (%s/s)\n", 82 format.FormatBytes(cache.bytesScanned), format.Duration(duration), 83 format.FormatBytes( 84 uint64(float64(cache.bytesScanned)/duration.Seconds()))) 85 } else if !fi.IsDir() { 86 return nil, 87 fmt.Errorf("%s exists but is not a directory", *objectsDirectory) 88 } else { 89 if err := cache.scanCache(*objectsDirectory, ""); err != nil { 90 return nil, err 91 } 92 } 93 missingObjects, requiredBytes, presentBytes := cache.computeMissing( 94 requiredObjects) 95 if len(missingObjects) < 1 { 96 logger.Debugln(0, "object cache already has all required objects") 97 return cache, nil 98 } 99 logger.Debugf(0, "object cache already has %d/%d objects (%s/%s)\n", 100 len(cache.objects), len(requiredObjects), 101 format.FormatBytes(presentBytes), format.FormatBytes(requiredBytes)) 102 err := cache.findAndScanUntrusted(missingObjects, rootDevice, logger) 103 if err != nil { 104 return nil, err 105 } 106 err = cache.downloadMissing(requiredObjects, objGetter, logger) 107 if err != nil { 108 return nil, err 109 } 110 return cache, nil 111 } 112 113 func (cache *objectsCache) downloadMissing(requiredObjects map[hash.Hash]uint64, 114 objGetter objectserver.ObjectsGetter, logger log.DebugLogger) error { 115 missingObjects, _, _ := cache.computeMissing(requiredObjects) 116 if len(missingObjects) < 1 { 117 return nil 118 } 119 hashes := make([]hash.Hash, 0, len(missingObjects)) 120 var totalBytes uint64 121 for hashVal, size := range missingObjects { 122 hashes = append(hashes, hashVal) 123 totalBytes += size 124 } 125 startTime := time.Now() 126 objectsReader, err := objGetter.GetObjects(hashes) 127 if err != nil { 128 return err 129 } 130 defer objectsReader.Close() 131 for _, hashVal := range hashes { 132 if err := cache.getNextObject(hashVal, objectsReader); err != nil { 133 return err 134 } 135 } 136 duration := time.Since(startTime) 137 logger.Debugf(0, "downloaded %d objects (%s) in %s (%s/s)\n", 138 len(missingObjects), format.FormatBytes(totalBytes), 139 format.Duration(duration), 140 format.FormatBytes(uint64(float64(totalBytes)/duration.Seconds()))) 141 return nil 142 } 143 144 func (cache *objectsCache) findAndScanUntrusted( 145 requiredObjects map[hash.Hash]uint64, rootDevice string, 146 logger log.DebugLogger) error { 147 if err := mount(rootDevice, *mountPoint, "ext4", logger); err != nil { 148 return nil 149 } 150 defer syscall.Unmount(*mountPoint, 0) 151 logger.Debugln(0, "scanning old root") 152 cache.bytesScanned = 0 153 startTime := time.Now() 154 foundObjects := make(map[hash.Hash]uint64) 155 err := cache.scanTree(*mountPoint, true, requiredObjects, foundObjects) 156 if err != nil { 157 return err 158 } 159 var requiredBytes, foundBytes uint64 160 for _, size := range requiredObjects { 161 requiredBytes += size 162 } 163 for _, size := range foundObjects { 164 foundBytes += size 165 } 166 duration := time.Since(startTime) 167 logger.Debugf(0, "scanned old root %s in %s (%s/s)\n", 168 format.FormatBytes(cache.bytesScanned), format.Duration(duration), 169 format.FormatBytes( 170 uint64(float64(cache.bytesScanned)/duration.Seconds()))) 171 logger.Debugf(0, "found %d/%d objects (%s/%s) in old file-system in %s\n", 172 len(foundObjects), len(requiredObjects), 173 format.FormatBytes(foundBytes), format.FormatBytes(requiredBytes), 174 format.Duration(duration)) 175 return nil 176 } 177 178 func (cache *objectsCache) GetObjects(hashes []hash.Hash) ( 179 objectserver.ObjectsReader, error) { 180 return &objectsReader{cache, hashes}, nil 181 } 182 183 func (cache *objectsCache) getNextObject(hashVal hash.Hash, 184 objectsReader objectserver.ObjectsReader) error { 185 size, reader, err := objectsReader.NextObject() 186 if err != nil { 187 return err 188 } 189 hashName := filepath.Join(*objectsDirectory, 190 objectcache.HashToFilename(hashVal)) 191 if err := os.MkdirAll(filepath.Dir(hashName), fsutil.DirPerms); err != nil { 192 return err 193 } 194 defer reader.Close() 195 writer, err := os.Create(hashName) 196 if err != nil { 197 return err 198 } 199 defer writer.Close() 200 if _, err := io.Copy(writer, reader); err != nil { 201 return err 202 } 203 cache.objects[hashVal] = size 204 return nil 205 } 206 207 func (cache *objectsCache) handleFile(filename string, copy bool, 208 requiredObjects, foundObjects map[hash.Hash]uint64) error { 209 if hashVal, size, err := hashFile(filename); err != nil { 210 return err 211 } else if size < 1 { 212 return nil 213 } else { 214 cache.bytesScanned += size 215 if _, ok := cache.objects[hashVal]; ok { 216 return nil 217 } 218 if _, ok := requiredObjects[hashVal]; !ok { 219 return nil 220 } 221 cache.objects[hashVal] = size 222 if foundObjects != nil { 223 foundObjects[hashVal] = size 224 } 225 hashName := filepath.Join(*objectsDirectory, 226 objectcache.HashToFilename(hashVal)) 227 err := os.MkdirAll(filepath.Dir(hashName), fsutil.DirPerms) 228 if err != nil { 229 return err 230 } 231 if copy { 232 reader, err := os.Open(filename) 233 if err != nil { 234 return err 235 } 236 defer reader.Close() 237 writer, err := os.Create(hashName) 238 if err != nil { 239 return err 240 } 241 defer writer.Close() 242 if _, err := io.Copy(writer, reader); err != nil { 243 return err 244 } 245 return nil 246 } 247 return os.Symlink(filename, hashName) 248 } 249 } 250 251 func (cache *objectsCache) scanCache(topDir, subpath string) error { 252 myPathName := filepath.Join(topDir, subpath) 253 file, err := os.Open(myPathName) 254 if err != nil { 255 return err 256 } 257 names, err := file.Readdirnames(-1) 258 file.Close() 259 if err != nil { 260 return err 261 } 262 for _, name := range names { 263 pathname := filepath.Join(myPathName, name) 264 fi, err := os.Stat(pathname) 265 if err != nil { 266 return err 267 } 268 filename := filepath.Join(subpath, name) 269 if fi.IsDir() { 270 if err := cache.scanCache(topDir, filename); err != nil { 271 return err 272 } 273 } else { 274 hashVal, err := objectcache.FilenameToHash(filename) 275 if err != nil { 276 return err 277 } 278 cache.objects[hashVal] = uint64(fi.Size()) 279 } 280 } 281 return nil 282 } 283 284 func (cache *objectsCache) scanRoot( 285 requiredObjects map[hash.Hash]uint64) error { 286 if err := os.Mkdir(*objectsDirectory, fsutil.DirPerms); err != nil { 287 return err 288 } 289 err := wsyscall.Mount("none", *objectsDirectory, "tmpfs", 0, "") 290 if err != nil { 291 return err 292 } 293 if err := cache.scanTree("/", false, requiredObjects, nil); err != nil { 294 return err 295 } 296 return nil 297 } 298 299 func (cache *objectsCache) scanTree(topDir string, copy bool, 300 requiredObjects, foundObjects map[hash.Hash]uint64) error { 301 var rootStat syscall.Stat_t 302 if err := syscall.Lstat(topDir, &rootStat); err != nil { 303 return err 304 } 305 return cache.walk(topDir, rootStat.Dev, copy, requiredObjects, foundObjects) 306 } 307 308 func (cache *objectsCache) walk(dirname string, device uint64, copy bool, 309 requiredObjects, foundObjects map[hash.Hash]uint64) error { 310 file, err := os.Open(dirname) 311 if err != nil { 312 return err 313 } 314 names, err := file.Readdirnames(-1) 315 file.Close() 316 if err != nil { 317 return err 318 } 319 for _, name := range names { 320 pathname := filepath.Join(dirname, name) 321 var stat syscall.Stat_t 322 err := syscall.Lstat(pathname, &stat) 323 if err != nil { 324 return err 325 } 326 if stat.Mode&syscall.S_IFMT == syscall.S_IFDIR { 327 if stat.Dev != device { 328 continue 329 } 330 err := cache.walk(pathname, device, copy, requiredObjects, 331 foundObjects) 332 if err != nil { 333 return err 334 } 335 } else if stat.Mode&syscall.S_IFMT == syscall.S_IFREG { 336 err := cache.handleFile(pathname, copy, requiredObjects, 337 foundObjects) 338 if err != nil { 339 return err 340 } 341 } 342 } 343 return nil 344 } 345 346 func (or *objectsReader) Close() error { 347 return nil 348 } 349 350 func (or *objectsReader) NextObject() (uint64, io.ReadCloser, error) { 351 if len(or.hashes) < 1 { 352 return 0, nil, errors.New("all objects have been consumed") 353 } 354 hashVal := or.hashes[0] 355 or.hashes = or.hashes[1:] 356 hashName := filepath.Join(*objectsDirectory, 357 objectcache.HashToFilename(hashVal)) 358 if file, err := os.Open(hashName); err != nil { 359 return 0, nil, err 360 } else { 361 return or.cache.objects[hashVal], file, nil 362 } 363 }