github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/imageserver/scanner/load.go (about) 1 package scanner 2 3 import ( 4 "encoding/gob" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "path" 10 "strings" 11 "syscall" 12 "time" 13 14 "github.com/Cloud-Foundations/Dominator/lib/concurrent" 15 "github.com/Cloud-Foundations/Dominator/lib/fsutil" 16 "github.com/Cloud-Foundations/Dominator/lib/image" 17 "github.com/Cloud-Foundations/Dominator/lib/log" 18 "github.com/Cloud-Foundations/Dominator/lib/log/logutil" 19 "github.com/Cloud-Foundations/Dominator/lib/log/prefixlogger" 20 "github.com/Cloud-Foundations/Dominator/lib/objectserver" 21 objectclient "github.com/Cloud-Foundations/Dominator/lib/objectserver/client" 22 "github.com/Cloud-Foundations/Dominator/lib/srpc" 23 "github.com/Cloud-Foundations/Dominator/lib/stringutil" 24 ) 25 26 func loadImageDataBase(baseDir string, objSrv objectserver.FullObjectServer, 27 replicationMaster string, logger log.DebugLogger) (*ImageDataBase, error) { 28 fi, err := os.Stat(baseDir) 29 if err != nil { 30 return nil, errors.New( 31 fmt.Sprintf("Cannot stat: %s: %s\n", baseDir, err)) 32 } 33 if !fi.IsDir() { 34 return nil, errors.New(fmt.Sprintf("%s is not a directory\n", baseDir)) 35 } 36 imdb := &ImageDataBase{ 37 baseDir: baseDir, 38 directoryMap: make(map[string]image.DirectoryMetadata), 39 imageMap: make(map[string]*image.Image), 40 addNotifiers: make(notifiers), 41 deleteNotifiers: make(notifiers), 42 mkdirNotifiers: make(makeDirectoryNotifiers), 43 deduper: stringutil.NewStringDeduplicator(false), 44 objectServer: objSrv, 45 replicationMaster: replicationMaster, 46 logger: logger, 47 } 48 imdb.unreferencedObjects, err = loadUnreferencedObjects( 49 path.Join(baseDir, unreferencedObjectsFile)) 50 if err != nil { 51 return nil, errors.New("error loading unreferenced objects list: " + 52 err.Error()) 53 } 54 state := concurrent.NewState(0) 55 startTime := time.Now() 56 var rusageStart, rusageStop syscall.Rusage 57 syscall.Getrusage(syscall.RUSAGE_SELF, &rusageStart) 58 if err := imdb.scanDirectory(".", state, logger); err != nil { 59 return nil, err 60 } 61 if err := state.Reap(); err != nil { 62 return nil, err 63 } 64 if logger != nil { 65 plural := "" 66 if imdb.CountImages() != 1 { 67 plural = "s" 68 } 69 syscall.Getrusage(syscall.RUSAGE_SELF, &rusageStop) 70 userTime := time.Duration(rusageStop.Utime.Sec)*time.Second + 71 time.Duration(rusageStop.Utime.Usec)*time.Microsecond - 72 time.Duration(rusageStart.Utime.Sec)*time.Second - 73 time.Duration(rusageStart.Utime.Usec)*time.Microsecond 74 logger.Printf("Loaded %d image%s in %s (%s user CPUtime)\n", 75 imdb.CountImages(), plural, time.Since(startTime), userTime) 76 logutil.LogMemory(logger, 0, "after loading") 77 } 78 imdb.regenerateUnreferencedObjectsList() 79 if ads, ok := objSrv.(objectserver.AddCallbackSetter); ok { 80 ads.SetAddCallback(imdb.garbageCollectorAddCallback) 81 } 82 if gcs, ok := objSrv.(objectserver.GarbageCollectorSetter); ok { 83 gcs.SetGarbageCollector(imdb.garbageCollector) 84 } 85 go imdb.periodicGarbageCollector() 86 return imdb, nil 87 } 88 89 func (imdb *ImageDataBase) scanDirectory(dirname string, 90 state *concurrent.State, logger log.DebugLogger) error { 91 directoryMetadata, err := imdb.readDirectoryMetadata(dirname) 92 if err != nil { 93 return err 94 } 95 imdb.directoryMap[dirname] = directoryMetadata 96 file, err := os.Open(path.Join(imdb.baseDir, dirname)) 97 if err != nil { 98 return err 99 } 100 names, err := file.Readdirnames(-1) 101 file.Close() 102 if err != nil { 103 return err 104 } 105 for _, name := range names { 106 if len(name) > 0 && name[0] == '.' { 107 continue // Skip hidden paths. 108 } 109 filename := path.Join(dirname, name) 110 var stat syscall.Stat_t 111 err := syscall.Lstat(path.Join(imdb.baseDir, filename), &stat) 112 if err != nil { 113 if err == syscall.ENOENT { 114 continue 115 } 116 return err 117 } 118 if stat.Mode&syscall.S_IFMT == syscall.S_IFDIR { 119 err = imdb.scanDirectory(filename, state, logger) 120 } else if stat.Mode&syscall.S_IFMT == syscall.S_IFREG && stat.Size > 0 { 121 err = state.GoRun(func() error { 122 return imdb.loadFile(filename, logger) 123 }) 124 } 125 if err != nil { 126 if err == syscall.ENOENT { 127 continue 128 } 129 return err 130 } 131 } 132 return nil 133 } 134 135 func (imdb *ImageDataBase) readDirectoryMetadata(dirname string) ( 136 image.DirectoryMetadata, error) { 137 file, err := os.Open(path.Join(imdb.baseDir, dirname, metadataFile)) 138 if err != nil { 139 if os.IsNotExist(err) { 140 return image.DirectoryMetadata{}, nil 141 } 142 return image.DirectoryMetadata{}, err 143 } 144 defer file.Close() 145 reader := fsutil.NewChecksumReader(file) 146 decoder := gob.NewDecoder(reader) 147 metadata := image.DirectoryMetadata{} 148 if err := decoder.Decode(&metadata); err != nil { 149 return image.DirectoryMetadata{}, fmt.Errorf( 150 "unable to read directory metadata for \"%s\": %s", dirname, err) 151 } 152 return metadata, reader.VerifyChecksum() 153 } 154 155 func (imdb *ImageDataBase) loadFile(filename string, 156 logger log.DebugLogger) error { 157 pathname := path.Join(imdb.baseDir, filename) 158 file, err := os.Open(pathname) 159 if err != nil { 160 return err 161 } 162 defer file.Close() 163 reader := fsutil.NewChecksumReader(file) 164 decoder := gob.NewDecoder(reader) 165 var img image.Image 166 if err := decoder.Decode(&img); err != nil { 167 return err 168 } 169 if err := reader.VerifyChecksum(); err != nil { 170 if err == fsutil.ErrorChecksumMismatch { 171 logger.Printf("Checksum mismatch for image: %s\n", filename) 172 return nil 173 } 174 if err != io.EOF { 175 return err 176 } 177 } 178 if imageIsExpired(&img) { 179 imdb.logger.Printf("Deleting already expired image: %s\n", filename) 180 return os.Remove(pathname) 181 } 182 if err := img.VerifyObjects(imdb.objectServer); err != nil { 183 if imdb.replicationMaster == "" || 184 !strings.Contains(err.Error(), "not available") { 185 return fmt.Errorf("error verifying: %s: %s", filename, err) 186 } 187 err = imdb.fetchMissingObjects(&img, 188 prefixlogger.New(filename+": ", logger)) 189 if err != nil { 190 return err 191 } 192 } 193 img.FileSystem.RebuildInodePointers() 194 imdb.deduperLock.Lock() 195 img.ReplaceStrings(imdb.deduper.DeDuplicate) 196 imdb.deduperLock.Unlock() 197 if err := img.Verify(); err != nil { 198 return err 199 } 200 imdb.scheduleExpiration(&img, filename) 201 imdb.Lock() 202 defer imdb.Unlock() 203 imdb.imageMap[filename] = &img 204 return nil 205 } 206 207 func (imdb *ImageDataBase) fetchMissingObjects(img *image.Image, 208 logger log.DebugLogger) error { 209 imdb.objectFetchLock.Lock() 210 defer imdb.objectFetchLock.Unlock() 211 client, err := srpc.DialHTTP("tcp", imdb.replicationMaster, time.Minute) 212 if err != nil { 213 return err 214 } 215 defer client.Close() 216 objClient := objectclient.AttachObjectClient(client) 217 defer objClient.Close() 218 return img.GetMissingObjects(imdb.objectServer, objClient, logger) 219 }