github.com/Cloud-Foundations/Dominator@v0.3.4/dom/lib/buildUpdateRequest.go (about) 1 package lib 2 3 import ( 4 "path" 5 "sort" 6 "time" 7 8 "github.com/Cloud-Foundations/Dominator/lib/filesystem" 9 "github.com/Cloud-Foundations/Dominator/lib/hash" 10 "github.com/Cloud-Foundations/Dominator/lib/image" 11 "github.com/Cloud-Foundations/Dominator/lib/log" 12 "github.com/Cloud-Foundations/Dominator/lib/log/debuglogger" 13 "github.com/Cloud-Foundations/Dominator/lib/stringutil" 14 subproto "github.com/Cloud-Foundations/Dominator/proto/sub" 15 ) 16 17 // Returns true if there is a failure due to missing computed files. 18 func (sub *Sub) buildUpdateRequest(img *image.Image, 19 request *subproto.UpdateRequest, deleteMissingComputedFiles bool, 20 ignoreMissingComputedFiles bool, slogger log.Logger) bool { 21 logger := debuglogger.Upgrade(slogger) 22 sub.requiredFS = img.FileSystem 23 sub.filter = img.Filter 24 request.Triggers = img.Triggers 25 sub.requiredInodeToSubInode = make(map[uint64]uint64) 26 sub.inodesMapped = make(map[uint64]struct{}) 27 sub.inodesChanged = make(map[uint64]struct{}) 28 sub.inodesCreated = make(map[uint64]string) 29 sub.subObjectCacheUsage = make(map[hash.Hash]uint64, len(sub.ObjectCache)) 30 // Populate subObjectCacheUsage. 31 for _, hash := range sub.ObjectCache { 32 sub.subObjectCacheUsage[hash] = 0 33 } 34 if !filesystem.CompareDirectoriesMetadata(&sub.FileSystem.DirectoryInode, 35 &sub.requiredFS.DirectoryInode, nil) { 36 makeDirectory(request, &sub.requiredFS.DirectoryInode, "/", false) 37 } 38 if sub.compareDirectories(request, 39 &sub.FileSystem.DirectoryInode, &sub.requiredFS.DirectoryInode, 40 "/", deleteMissingComputedFiles, ignoreMissingComputedFiles, logger) { 41 return true 42 } 43 // Look for multiply used objects and tell the sub. 44 for obj, useCount := range sub.subObjectCacheUsage { 45 if useCount > 1 { 46 if request.MultiplyUsedObjects == nil { 47 request.MultiplyUsedObjects = make(map[hash.Hash]uint64) 48 } 49 request.MultiplyUsedObjects[obj] = useCount 50 } 51 } 52 return false 53 } 54 55 // Returns true if there is a failure due to missing computed files. 56 func (sub *Sub) compareDirectories(request *subproto.UpdateRequest, 57 subDirectory, requiredDirectory *filesystem.DirectoryInode, 58 myPathName string, deleteMissingComputedFiles bool, 59 ignoreMissingComputedFiles bool, logger log.DebugLogger) bool { 60 // First look for entries that should be deleted. 61 if sub.filter != nil && subDirectory != nil { 62 for name := range subDirectory.EntriesByName { 63 pathname := path.Join(myPathName, name) 64 if sub.filter.Match(pathname) { 65 continue 66 } 67 if _, ok := requiredDirectory.EntriesByName[name]; !ok { 68 request.PathsToDelete = append(request.PathsToDelete, pathname) 69 } 70 } 71 } 72 // For the love of repeatable unit tests, sort before looping. 73 names := make([]string, 0, len(requiredDirectory.EntriesByName)) 74 for name := range requiredDirectory.EntriesByName { 75 names = append(names, name) 76 } 77 sort.Strings(names) 78 for _, name := range names { 79 requiredEntry := requiredDirectory.EntriesByName[name] 80 pathname := path.Join(myPathName, name) 81 if sub.filter != nil && sub.filter.Match(pathname) { 82 continue 83 } 84 var subEntry *filesystem.DirectoryEntry 85 if subDirectory != nil { 86 if se, ok := subDirectory.EntriesByName[name]; ok { 87 subEntry = se 88 } 89 } 90 requiredInode := requiredEntry.Inode() 91 if _, ok := requiredInode.(*filesystem.ComputedRegularInode); ok { 92 // Replace with computed file. 93 inode, ok := sub.ComputedInodes[pathname] 94 if !ok { 95 if deleteMissingComputedFiles { 96 if subEntry != nil { 97 request.PathsToDelete = append(request.PathsToDelete, 98 pathname) 99 } 100 continue 101 } 102 if ignoreMissingComputedFiles { 103 continue 104 } 105 logger.Printf( 106 "compareDirectories(%s): missing computed file: %s\n", 107 sub, pathname) 108 return true 109 } 110 setComputedFileMtime(inode, subEntry) 111 newEntry := new(filesystem.DirectoryEntry) 112 newEntry.Name = name 113 newEntry.InodeNumber = requiredEntry.InodeNumber 114 newEntry.SetInode(inode) 115 requiredEntry = newEntry 116 } 117 if subEntry == nil { 118 sub.addEntry(request, requiredEntry, pathname, logger) 119 } else { 120 sub.compareEntries(request, subEntry, requiredEntry, pathname, 121 logger) 122 } 123 // If a directory: descend (possibly with the directory for the sub). 124 if requiredInode, ok := requiredInode.(*filesystem.DirectoryInode); ok { 125 var subInode *filesystem.DirectoryInode 126 if subEntry != nil { 127 if si, ok := subEntry.Inode().(*filesystem.DirectoryInode); ok { 128 subInode = si 129 } 130 } 131 sub.compareDirectories(request, subInode, requiredInode, pathname, 132 deleteMissingComputedFiles, ignoreMissingComputedFiles, logger) 133 } 134 } 135 return false 136 } 137 138 func setComputedFileMtime(requiredInode *filesystem.RegularInode, 139 subEntry *filesystem.DirectoryEntry) { 140 if requiredInode.MtimeSeconds >= 0 { 141 return 142 } 143 if subEntry != nil { 144 subInode := subEntry.Inode() 145 if subInode, ok := subInode.(*filesystem.RegularInode); ok { 146 if requiredInode.Hash == subInode.Hash { 147 requiredInode.MtimeNanoSeconds = subInode.MtimeNanoSeconds 148 requiredInode.MtimeSeconds = subInode.MtimeSeconds 149 return 150 } 151 } 152 } 153 requiredInode.MtimeSeconds = time.Now().Unix() 154 } 155 156 func (sub *Sub) addEntry(request *subproto.UpdateRequest, 157 requiredEntry *filesystem.DirectoryEntry, myPathName string, 158 logger log.DebugLogger) { 159 requiredInode := requiredEntry.Inode() 160 if requiredInode, ok := requiredInode.(*filesystem.DirectoryInode); ok { 161 makeDirectory(request, requiredInode, myPathName, true) 162 } else { 163 sub.addInode(request, requiredEntry, myPathName, logger) 164 } 165 } 166 167 func (sub *Sub) compareEntries(request *subproto.UpdateRequest, 168 subEntry, requiredEntry *filesystem.DirectoryEntry, myPathName string, 169 logger log.DebugLogger) { 170 subInode := subEntry.Inode() 171 requiredInode := requiredEntry.Inode() 172 sameType, sameMetadata, sameData := filesystem.CompareInodes( 173 subInode, requiredInode, nil) 174 if requiredInode, ok := requiredInode.(*filesystem.DirectoryInode); ok { 175 if sameMetadata { 176 return 177 } 178 if sameType { 179 makeDirectory(request, requiredInode, myPathName, false) 180 } else { 181 makeDirectory(request, requiredInode, myPathName, true) 182 } 183 return 184 } 185 if sameType && sameData && sameMetadata { 186 if sub.relink(request, subEntry, requiredEntry, myPathName, logger) { 187 logger.Debugf(0, "identical relink OK for %s\n", myPathName) 188 return 189 } 190 } else if sameType && sameData && 191 sub.compareInodeLinks(requiredEntry.InodeNumber, subEntry.InodeNumber) { 192 if sub.relink(request, subEntry, requiredEntry, myPathName, 193 logger) { 194 logger.Debugf(0, "mapped and changed for %s, inum: %d\n", 195 myPathName, subEntry.InodeNumber) 196 sub.updateMetadata(request, requiredEntry, myPathName) 197 return 198 } 199 } 200 sub.addInode(request, requiredEntry, myPathName, logger) 201 } 202 203 func (sub *Sub) relink(request *subproto.UpdateRequest, 204 subEntry, requiredEntry *filesystem.DirectoryEntry, 205 myPathName string, logger log.DebugLogger) bool { 206 subInum, ok := sub.requiredInodeToSubInode[requiredEntry.InodeNumber] 207 if !ok { 208 if _, mapped := sub.inodesMapped[subEntry.InodeNumber]; mapped { 209 return false 210 } 211 sub.requiredInodeToSubInode[requiredEntry.InodeNumber] = 212 subEntry.InodeNumber 213 logger.Debugf(0, "mapping required inum: %d to sub inum: %d for: %s\n", 214 requiredEntry.InodeNumber, subEntry.InodeNumber, myPathName) 215 sub.inodesMapped[subEntry.InodeNumber] = struct{}{} 216 return true 217 } 218 if subInum == subEntry.InodeNumber { 219 return true 220 } 221 makeHardlink(request, 222 myPathName, sub.FileSystem.InodeToFilenamesTable()[subInum][0]) 223 return true 224 } 225 226 func makeHardlink(request *subproto.UpdateRequest, newLink, target string) { 227 var hardlink subproto.Hardlink 228 hardlink.NewLink = newLink 229 hardlink.Target = target 230 request.HardlinksToMake = append(request.HardlinksToMake, hardlink) 231 } 232 233 func (sub *Sub) compareInodeLinks(requiredInum, subInum uint64) bool { 234 requiredNames := sub.requiredFS.InodeToFilenamesTable()[requiredInum] 235 subNames := sub.FileSystem.InodeToFilenamesTable()[subInum] 236 if len(subNames) > len(requiredNames) { 237 return false 238 } 239 requiredLinks := stringutil.ConvertListToMap(requiredNames, false) 240 for _, name := range subNames { 241 if _, ok := requiredLinks[name]; !ok { 242 return false 243 } 244 } 245 return true 246 } 247 248 func (sub *Sub) updateMetadata(request *subproto.UpdateRequest, 249 requiredEntry *filesystem.DirectoryEntry, myPathName string) { 250 if _, ok := sub.inodesChanged[requiredEntry.InodeNumber]; ok { 251 return 252 } 253 var inode subproto.Inode 254 inode.Name = myPathName 255 inode.GenericInode = requiredEntry.Inode() 256 request.InodesToChange = append(request.InodesToChange, inode) 257 sub.inodesChanged[requiredEntry.InodeNumber] = struct{}{} 258 } 259 260 func makeDirectory(request *subproto.UpdateRequest, 261 requiredInode *filesystem.DirectoryInode, pathName string, create bool) { 262 var newInode subproto.Inode 263 newInode.Name = pathName 264 var newDirectoryInode filesystem.DirectoryInode 265 newDirectoryInode.Mode = requiredInode.Mode 266 newDirectoryInode.Uid = requiredInode.Uid 267 newDirectoryInode.Gid = requiredInode.Gid 268 newInode.GenericInode = &newDirectoryInode 269 if create { 270 request.DirectoriesToMake = append(request.DirectoriesToMake, newInode) 271 } else { 272 request.InodesToChange = append(request.InodesToChange, newInode) 273 } 274 } 275 276 func (sub *Sub) addInode(request *subproto.UpdateRequest, 277 requiredEntry *filesystem.DirectoryEntry, myPathName string, 278 logger log.DebugLogger) { 279 requiredInode := requiredEntry.Inode() 280 logger.Debugf(0, "addInode(%s, %d) Uid=%d\n", 281 myPathName, requiredEntry.InodeNumber, requiredInode.GetUid()) 282 if name, ok := sub.inodesCreated[requiredEntry.InodeNumber]; ok { 283 logger.Debugf(0, "make link: %s to %s\n", myPathName, name) 284 makeHardlink(request, myPathName, name) 285 return 286 } 287 // Try to find a sibling inode. 288 names := sub.requiredFS.InodeToFilenamesTable()[requiredEntry.InodeNumber] 289 subFS := sub.FileSystem 290 if len(names) > 1 { 291 for _, name := range names { 292 if name == myPathName { 293 logger.Debugf(0, "skipping self comparison: %s\n", name) 294 continue 295 } 296 if inum, found := subFS.FilenameToInodeTable()[name]; found { 297 subInode := sub.FileSystem.InodeTable[inum] 298 _, sameMetadata, sameData := filesystem.CompareInodes( 299 subInode, requiredInode, nil) 300 if sameMetadata && sameData { 301 logger.Debugf(0, "make sibling link: %s to %s (uid=%d)\n", 302 myPathName, name, subInode.GetUid()) 303 makeHardlink(request, myPathName, name) 304 return 305 } 306 } 307 } 308 } 309 if inode, ok := requiredEntry.Inode().(*filesystem.RegularInode); ok { 310 if inode.Size > 0 { 311 if old, ok := sub.subObjectCacheUsage[inode.Hash]; ok { 312 sub.subObjectCacheUsage[inode.Hash]++ 313 if old > 0 { 314 logger.Debugf(0, "duplicate in cache for: %s\n", myPathName) 315 } 316 } else { 317 // Not in object cache: grab it from file-system. 318 logger.Debugf(0, "copy to cache for: %s\n", myPathName) 319 request.FilesToCopyToCache = append( 320 request.FilesToCopyToCache, 321 sub.getFileToCopy(myPathName, inode.Hash)) 322 sub.subObjectCacheUsage[inode.Hash] = 1 323 } 324 } 325 } 326 var inode subproto.Inode 327 inode.Name = myPathName 328 inode.GenericInode = requiredEntry.Inode() 329 request.InodesToMake = append(request.InodesToMake, inode) 330 sub.inodesCreated[requiredEntry.InodeNumber] = myPathName 331 } 332 333 func (sub *Sub) getFileToCopy(myPathName string, 334 hashVal hash.Hash) subproto.FileToCopyToCache { 335 subFS := sub.FileSystem 336 requiredFS := sub.requiredFS 337 inos, ok := subFS.HashToInodesTable()[hashVal] 338 if !ok { 339 panic("No object in cache for: " + myPathName) 340 } 341 file := subproto.FileToCopyToCache{ 342 Name: subFS.InodeToFilenamesTable()[inos[0]][0], 343 Hash: hashVal, 344 } 345 // Try to find an inode where all its links will be deleted and mark one of 346 // the links (filenames) to be hardlinked instead of copied into the cache. 347 for _, iNum := range inos { 348 filenames := subFS.InodeToFilenamesTable()[iNum] 349 for _, filename := range filenames { 350 if _, ok := requiredFS.FilenameToInodeTable()[filename]; ok { 351 filenames = nil 352 break 353 } 354 if sub.filter == nil || sub.filter.Match(filename) { 355 filenames = nil 356 break 357 } 358 } 359 if filenames != nil { 360 file.DoHardlink = true 361 break 362 } 363 } 364 return file 365 }