github.com/Cloud-Foundations/Dominator@v0.3.4/sub/lib/update.go (about) 1 package lib 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "strings" 11 "syscall" 12 "time" 13 14 "github.com/Cloud-Foundations/Dominator/lib/constants" 15 "github.com/Cloud-Foundations/Dominator/lib/filesystem" 16 "github.com/Cloud-Foundations/Dominator/lib/filesystem/scanner" 17 "github.com/Cloud-Foundations/Dominator/lib/filter" 18 "github.com/Cloud-Foundations/Dominator/lib/fsutil" 19 "github.com/Cloud-Foundations/Dominator/lib/hash" 20 "github.com/Cloud-Foundations/Dominator/lib/log" 21 "github.com/Cloud-Foundations/Dominator/lib/objectcache" 22 "github.com/Cloud-Foundations/Dominator/lib/triggers" 23 "github.com/Cloud-Foundations/Dominator/lib/wsyscall" 24 "github.com/Cloud-Foundations/Dominator/proto/sub" 25 ) 26 27 func (t *uType) update(request sub.UpdateRequest) error { 28 if request.Triggers == nil { 29 request.Triggers = triggers.New() 30 } 31 if t.SkipFilter == nil { 32 t.SkipFilter = new(filter.Filter) 33 } 34 t.copyFilesToCache(request.FilesToCopyToCache) 35 t.makeObjectCopies(request.MultiplyUsedObjects) 36 if t.RunTriggers != nil && 37 t.OldTriggers != nil && len(t.OldTriggers.Triggers) > 0 { 38 t.makeDirectories(request.DirectoriesToMake, 39 t.OldTriggers, false) 40 t.makeInodes(request.InodesToMake, request.MultiplyUsedObjects, 41 t.OldTriggers, false) 42 t.makeHardlinks(request.HardlinksToMake, t.OldTriggers, false) 43 t.doDeletes(request.PathsToDelete, t.OldTriggers, false) 44 t.changeInodes(request.InodesToChange, t.OldTriggers, false) 45 matchedOldTriggers := t.OldTriggers.GetMatchedTriggers() 46 err := t.checkDisruption(matchedOldTriggers, request.ForceDisruption) 47 if err != nil { 48 return err 49 } 50 if t.RunTriggers(matchedOldTriggers, "stop", t.Logger) { 51 t.hadTriggerFailures = true 52 } 53 } 54 fsChangeStartTime := time.Now() 55 t.makeDirectories(request.DirectoriesToMake, request.Triggers, true) 56 t.makeInodes(request.InodesToMake, request.MultiplyUsedObjects, 57 request.Triggers, true) 58 t.makeHardlinks(request.HardlinksToMake, request.Triggers, true) 59 t.doDeletes(request.PathsToDelete, request.Triggers, true) 60 t.changeInodes(request.InodesToChange, request.Triggers, true) 61 if err := t.writePatchedImageName(request.ImageName); err != nil { 62 t.Logger.Println(err) 63 } 64 t.fsChangeDuration = time.Since(fsChangeStartTime) 65 matchedNewTriggers := request.Triggers.GetMatchedTriggers() 66 if t.RunTriggers != nil && 67 t.RunTriggers(matchedNewTriggers, "start", t.Logger) { 68 t.hadTriggerFailures = true 69 } 70 return t.lastError 71 } 72 73 func (t *uType) checkDisruption(matchedTriggers []*triggers.Trigger, 74 force bool) error { 75 if t.DisruptionRequest == nil && t.DisruptionCancel == nil { 76 return nil 77 } 78 if !isHighImpact(matchedTriggers) { 79 if t.DisruptionCancel != nil { 80 t.DisruptionCancel() 81 } 82 return nil 83 } 84 if force { 85 return nil 86 } 87 if t.DisruptionRequest == nil { 88 return nil 89 } 90 switch t.DisruptionRequest() { 91 case sub.DisruptionStateAnytime: 92 return nil 93 case sub.DisruptionStatePermitted: 94 return nil 95 case sub.DisruptionStateRequested: 96 return errors.New(sub.ErrorDisruptionPending) 97 case sub.DisruptionStateDenied: 98 return errors.New(sub.ErrorDisruptionDenied) 99 default: 100 return nil 101 } 102 } 103 104 func isHighImpact(matchedTriggers []*triggers.Trigger) bool { 105 if len(matchedTriggers) < 1 { 106 return false 107 } 108 for _, trigger := range matchedTriggers { 109 if trigger.HighImpact { 110 return true 111 } 112 } 113 return false 114 } 115 116 func (t *uType) copyFilesToCache(filesToCopyToCache []sub.FileToCopyToCache) { 117 for _, fileToCopy := range filesToCopyToCache { 118 sourcePathname := filepath.Join(t.RootDirectoryName, fileToCopy.Name) 119 destPathname := filepath.Join(t.ObjectsDir, 120 objectcache.HashToFilename(fileToCopy.Hash)) 121 prefix := "Copied" 122 if fileToCopy.DoHardlink { 123 prefix = "Hardlinked" 124 } 125 if err := copyFile(destPathname, sourcePathname, 126 fileToCopy.DoHardlink); err != nil { 127 t.lastError = err 128 t.Logger.Println(err) 129 } else { 130 t.Logger.Printf("%s: %s to cache\n", prefix, sourcePathname) 131 } 132 } 133 } 134 135 func copyFile(destPathname, sourcePathname string, doHardlink bool) error { 136 dirname := filepath.Dir(destPathname) 137 if err := os.MkdirAll(dirname, syscall.S_IRWXU); err != nil { 138 return err 139 } 140 if doHardlink { 141 return fsutil.ForceLink(sourcePathname, destPathname) 142 } 143 sourceFile, err := os.Open(sourcePathname) 144 if err != nil { 145 return err 146 } 147 defer sourceFile.Close() 148 destFile, err := os.Create(destPathname) 149 if err != nil { 150 return err 151 } 152 defer destFile.Close() 153 _, err = io.Copy(destFile, sourceFile) 154 return err 155 } 156 157 func (t *uType) makeObjectCopies(multiplyUsedObjects map[hash.Hash]uint64) { 158 for hash, numCopies := range multiplyUsedObjects { 159 if numCopies < 2 { 160 continue 161 } 162 objectPathname := filepath.Join(t.ObjectsDir, 163 objectcache.HashToFilename(hash)) 164 for numCopies--; numCopies > 0; numCopies-- { 165 ext := fmt.Sprintf("~%d~", numCopies) 166 if err := copyFile(objectPathname+ext, objectPathname, 167 false); err != nil { 168 t.lastError = err 169 t.Logger.Println(err) 170 } else { 171 t.Logger.Printf("Copied object: %x%s\n", hash, ext) 172 } 173 } 174 } 175 } 176 177 func (t *uType) makeInodes(inodesToMake []sub.Inode, 178 multiplyUsedObjects map[hash.Hash]uint64, triggers *triggers.Triggers, 179 takeAction bool) { 180 for _, inode := range inodesToMake { 181 triggers.Match(inode.Name) 182 if takeAction { 183 fullPathname := filepath.Join(t.RootDirectoryName, inode.Name) 184 var err error 185 switch inode := inode.GenericInode.(type) { 186 case *filesystem.RegularInode: 187 err = makeRegularInode(fullPathname, inode, multiplyUsedObjects, 188 t.ObjectsDir, t.Logger) 189 case *filesystem.SymlinkInode: 190 err = makeSymlinkInode(fullPathname, inode, t.Logger) 191 case *filesystem.SpecialInode: 192 err = makeSpecialInode(fullPathname, inode, t.Logger) 193 } 194 if err != nil { 195 t.lastError = err 196 } 197 } 198 } 199 } 200 201 func makeRegularInode(fullPathname string, 202 inode *filesystem.RegularInode, multiplyUsedObjects map[hash.Hash]uint64, 203 objectsDir string, logger log.Logger) error { 204 var objectPathname string 205 if inode.Size > 0 { 206 objectPathname = filepath.Join(objectsDir, 207 objectcache.HashToFilename(inode.Hash)) 208 numCopies := multiplyUsedObjects[inode.Hash] 209 if numCopies > 1 { 210 numCopies-- 211 objectPathname += fmt.Sprintf("~%d~", numCopies) 212 if numCopies < 2 { 213 delete(multiplyUsedObjects, inode.Hash) 214 } else { 215 multiplyUsedObjects[inode.Hash] = numCopies 216 } 217 } 218 } else { 219 objectPathname = fmt.Sprintf("%s.empty.%d", fullPathname, os.Getpid()) 220 if file, err := os.OpenFile(objectPathname, 221 os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600); err != nil { 222 return err 223 } else { 224 file.Close() 225 } 226 } 227 if err := fsutil.ForceRename(objectPathname, fullPathname); err != nil { 228 logger.Println(err) 229 return err 230 } 231 if err := inode.WriteMetadata(fullPathname); err != nil { 232 logger.Println(err) 233 return err 234 } else { 235 if inode.Size > 0 { 236 logger.Printf("Made inode: %s from: %x\n", 237 fullPathname, inode.Hash) 238 } else { 239 logger.Printf("Made empty inode: %s\n", fullPathname) 240 } 241 } 242 return nil 243 } 244 245 func makeSymlinkInode(fullPathname string, 246 inode *filesystem.SymlinkInode, logger log.Logger) error { 247 if err := inode.Write(fullPathname); err != nil { 248 logger.Println(err) 249 return err 250 } 251 logger.Printf("Made symlink inode: %s -> %s\n", fullPathname, inode.Symlink) 252 return nil 253 } 254 255 func makeSpecialInode(fullPathname string, inode *filesystem.SpecialInode, 256 logger log.Logger) error { 257 if err := inode.Write(fullPathname); err != nil { 258 logger.Println(err) 259 return err 260 } 261 logger.Printf("Made special inode: %s\n", fullPathname) 262 return nil 263 } 264 265 func (t *uType) makeHardlinks(hardlinksToMake []sub.Hardlink, 266 triggers *triggers.Triggers, takeAction bool) { 267 tmpName := filepath.Join(t.ObjectsDir, "temporaryHardlink") 268 for _, hardlink := range hardlinksToMake { 269 triggers.Match(hardlink.NewLink) 270 if takeAction { 271 targetPathname := filepath.Join(t.RootDirectoryName, 272 hardlink.Target) 273 linkPathname := filepath.Join(t.RootDirectoryName, hardlink.NewLink) 274 // A Link directly to linkPathname will fail if it exists, so do a 275 // Link+Rename using a temporary filename. 276 if err := fsutil.ForceLink(targetPathname, tmpName); err != nil { 277 t.lastError = err 278 t.Logger.Println(err) 279 continue 280 } 281 if err := fsutil.ForceRename(tmpName, linkPathname); err != nil { 282 t.Logger.Println(err) 283 if err := fsutil.ForceRemove(tmpName); err != nil { 284 t.lastError = err 285 t.Logger.Println(err) 286 } 287 } else { 288 t.Logger.Printf("Linked: %s => %s\n", 289 linkPathname, targetPathname) 290 } 291 } 292 } 293 } 294 295 func (t *uType) doDeletes(pathsToDelete []string, triggers *triggers.Triggers, 296 takeAction bool) { 297 for _, pathname := range pathsToDelete { 298 triggers.Match(pathname) 299 if takeAction { 300 fullPathname := filepath.Join(t.RootDirectoryName, pathname) 301 if err := fsutil.ForceRemoveAll(fullPathname); err != nil { 302 t.lastError = err 303 t.Logger.Println(err) 304 } else { 305 t.Logger.Printf("Deleted: %s\n", fullPathname) 306 } 307 } 308 } 309 } 310 311 func (t *uType) makeDirectories(directoriesToMake []sub.Inode, 312 triggers *triggers.Triggers, takeAction bool) { 313 for _, newdir := range directoriesToMake { 314 if t.skipPath(newdir.Name) { 315 continue 316 } 317 triggers.Match(newdir.Name) 318 if takeAction { 319 fullPathname := filepath.Join(t.RootDirectoryName, newdir.Name) 320 inode, ok := newdir.GenericInode.(*filesystem.DirectoryInode) 321 if !ok { 322 t.Logger.Println("%s is not a directory!\n", newdir.Name) 323 continue 324 } 325 if err := inode.Write(fullPathname); err != nil { 326 t.lastError = err 327 t.Logger.Println(err) 328 } else { 329 t.Logger.Printf("Made directory: %s (mode=%s)\n", 330 fullPathname, inode.Mode) 331 } 332 } 333 } 334 } 335 336 func (t *uType) changeInodes(inodesToChange []sub.Inode, 337 triggers *triggers.Triggers, takeAction bool) { 338 for _, inode := range inodesToChange { 339 fullPathname := filepath.Join(t.RootDirectoryName, inode.Name) 340 if checkNonMtimeChange(fullPathname, inode.GenericInode) { 341 triggers.Match(inode.Name) 342 } 343 if takeAction { 344 if err := filesystem.ForceWriteMetadata(inode, 345 fullPathname); err != nil { 346 t.lastError = err 347 t.Logger.Println(err) 348 continue 349 } 350 t.Logger.Printf("Changed inode: %s\n", fullPathname) 351 } 352 } 353 } 354 355 func checkNonMtimeChange(filename string, inode filesystem.GenericInode) bool { 356 switch inode := inode.(type) { 357 case *filesystem.RegularInode: 358 var stat wsyscall.Stat_t 359 if err := wsyscall.Lstat(filename, &stat); err != nil { 360 return true 361 } 362 if stat.Mode&syscall.S_IFMT == syscall.S_IFREG { 363 oldInode := scanner.MakeRegularInode(&stat) 364 oldInode.Hash = inode.Hash 365 oldInode.MtimeNanoSeconds = inode.MtimeNanoSeconds 366 oldInode.MtimeSeconds = inode.MtimeSeconds 367 if *oldInode == *inode { 368 return false 369 } 370 } 371 case *filesystem.SpecialInode: 372 var stat wsyscall.Stat_t 373 if err := wsyscall.Lstat(filename, &stat); err != nil { 374 return true 375 } 376 if stat.Mode&syscall.S_IFMT == syscall.S_IFBLK || 377 stat.Mode&syscall.S_IFMT == syscall.S_IFCHR { 378 oldInode := scanner.MakeSpecialInode(&stat) 379 oldInode.MtimeNanoSeconds = inode.MtimeNanoSeconds 380 oldInode.MtimeSeconds = inode.MtimeSeconds 381 if *oldInode == *inode { 382 return false 383 } 384 } 385 } 386 return true 387 } 388 389 func (t *uType) skipPath(pathname string) bool { 390 if t.SkipFilter.Match(pathname) { 391 return true 392 } 393 if pathname == "/.subd" { 394 return true 395 } 396 if strings.HasPrefix(pathname, "/.subd/") { 397 return true 398 } 399 return false 400 } 401 402 func (t *uType) writePatchedImageName(imageName string) error { 403 pathname := filepath.Join(t.RootDirectoryName, 404 constants.PatchedImageNameFile) 405 if imageName == "" { 406 if err := os.Remove(pathname); err != nil { 407 if os.IsNotExist(err) { 408 return nil 409 } 410 return err 411 } 412 } 413 if err := os.MkdirAll(filepath.Dir(pathname), fsutil.DirPerms); err != nil { 414 return err 415 } 416 buffer := &bytes.Buffer{} 417 fmt.Fprintln(buffer, imageName) 418 return fsutil.CopyToFile(pathname, fsutil.PublicFilePerms, buffer, 0) 419 }