github.com/kayoticsully/syncthing@v0.8.9-0.20140724133906-c45a2fdc03f8/model/puller.go (about) 1 // Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file). 2 // All rights reserved. Use of this source code is governed by an MIT-style 3 // license that can be found in the LICENSE file. 4 5 package model 6 7 import ( 8 "bytes" 9 "errors" 10 "os" 11 "path/filepath" 12 "time" 13 14 "github.com/calmh/syncthing/config" 15 "github.com/calmh/syncthing/events" 16 "github.com/calmh/syncthing/osutil" 17 "github.com/calmh/syncthing/protocol" 18 "github.com/calmh/syncthing/scanner" 19 "github.com/calmh/syncthing/versioner" 20 ) 21 22 type requestResult struct { 23 node protocol.NodeID 24 file protocol.FileInfo 25 filepath string // full filepath name 26 offset int64 27 data []byte 28 err error 29 } 30 31 type openFile struct { 32 filepath string // full filepath name 33 temp string // temporary filename 34 availability []protocol.NodeID 35 file *os.File 36 err error // error when opening or writing to file, all following operations are cancelled 37 outstanding int // number of requests we still have outstanding 38 done bool // we have sent all requests for this file 39 } 40 41 type activityMap map[protocol.NodeID]int 42 43 func (m activityMap) leastBusyNode(availability []protocol.NodeID, isValid func(protocol.NodeID) bool) protocol.NodeID { 44 var low int = 2<<30 - 1 45 var selected protocol.NodeID 46 for _, node := range availability { 47 usage := m[node] 48 if usage < low && isValid(node) { 49 low = usage 50 selected = node 51 } 52 } 53 m[selected]++ 54 return selected 55 } 56 57 func (m activityMap) decrease(node protocol.NodeID) { 58 m[node]-- 59 } 60 61 var errNoNode = errors.New("no available source node") 62 63 type puller struct { 64 cfg *config.Configuration 65 repoCfg config.RepositoryConfiguration 66 bq blockQueue 67 slots int 68 model *Model 69 oustandingPerNode activityMap 70 openFiles map[string]openFile 71 requestSlots chan bool 72 blocks chan bqBlock 73 requestResults chan requestResult 74 versioner versioner.Versioner 75 } 76 77 func newPuller(repoCfg config.RepositoryConfiguration, model *Model, slots int, cfg *config.Configuration) *puller { 78 p := &puller{ 79 cfg: cfg, 80 repoCfg: repoCfg, 81 slots: slots, 82 model: model, 83 oustandingPerNode: make(activityMap), 84 openFiles: make(map[string]openFile), 85 requestSlots: make(chan bool, slots), 86 blocks: make(chan bqBlock), 87 requestResults: make(chan requestResult), 88 } 89 90 if len(repoCfg.Versioning.Type) > 0 { 91 factory, ok := versioner.Factories[repoCfg.Versioning.Type] 92 if !ok { 93 l.Fatalf("Requested versioning type %q that does not exist", repoCfg.Versioning.Type) 94 } 95 p.versioner = factory(repoCfg.Versioning.Params) 96 } 97 98 if slots > 0 { 99 // Read/write 100 if debug { 101 l.Debugf("starting puller; repo %q dir %q slots %d", repoCfg.ID, repoCfg.Directory, slots) 102 } 103 go p.run() 104 } else { 105 // Read only 106 if debug { 107 l.Debugf("starting puller; repo %q dir %q (read only)", repoCfg.ID, repoCfg.Directory) 108 } 109 go p.runRO() 110 } 111 return p 112 } 113 114 func (p *puller) run() { 115 changed := true 116 scanintv := time.Duration(p.cfg.Options.RescanIntervalS) * time.Second 117 lastscan := time.Now() 118 var prevVer uint64 119 var queued int 120 121 // Load up the request slots 122 for i := 0; i < cap(p.requestSlots); i++ { 123 p.requestSlots <- true 124 } 125 126 for { 127 // Run the pulling loop as long as there are blocks to fetch 128 129 prevVer, queued = p.queueNeededBlocks(prevVer) 130 if queued > 0 { 131 132 pull: 133 for { 134 select { 135 case res := <-p.requestResults: 136 p.model.setState(p.repoCfg.ID, RepoSyncing) 137 changed = true 138 p.requestSlots <- true 139 p.handleRequestResult(res) 140 141 case <-p.requestSlots: 142 b, ok := p.bq.get() 143 144 if !ok { 145 if debug { 146 l.Debugf("%q: pulling loop needs more blocks", p.repoCfg.ID) 147 } 148 prevVer, _ = p.queueNeededBlocks(prevVer) 149 b, ok = p.bq.get() 150 } 151 152 if !ok && len(p.openFiles) == 0 { 153 // Nothing queued, nothing outstanding 154 if debug { 155 l.Debugf("%q: pulling loop done", p.repoCfg.ID) 156 } 157 break pull 158 } 159 160 if !ok { 161 // Nothing queued, but there are still open files. 162 // Give the situation a moment to change. 163 if debug { 164 l.Debugf("%q: pulling loop paused", p.repoCfg.ID) 165 } 166 p.requestSlots <- true 167 time.Sleep(100 * time.Millisecond) 168 continue pull 169 } 170 171 if debug { 172 l.Debugf("queueing %q / %q offset %d copy %d", p.repoCfg.ID, b.file.Name, b.block.Offset, len(b.copy)) 173 } 174 p.model.setState(p.repoCfg.ID, RepoSyncing) 175 changed = true 176 if p.handleBlock(b) { 177 // Block was fully handled, free up the slot 178 p.requestSlots <- true 179 } 180 } 181 } 182 } 183 184 if changed { 185 p.model.setState(p.repoCfg.ID, RepoCleaning) 186 p.fixupDirectories() 187 changed = false 188 } 189 190 p.model.setState(p.repoCfg.ID, RepoIdle) 191 192 // Do a rescan if it's time for it 193 if time.Since(lastscan) > scanintv { 194 if debug { 195 l.Debugf("%q: time for rescan", p.repoCfg.ID) 196 } 197 198 err := p.model.ScanRepo(p.repoCfg.ID) 199 if err != nil { 200 invalidateRepo(p.cfg, p.repoCfg.ID, err) 201 return 202 } 203 lastscan = time.Now() 204 } 205 206 time.Sleep(5 * time.Second) 207 } 208 } 209 210 func (p *puller) runRO() { 211 walkTicker := time.Tick(time.Duration(p.cfg.Options.RescanIntervalS) * time.Second) 212 213 for _ = range walkTicker { 214 if debug { 215 l.Debugf("%q: time for rescan", p.repoCfg.ID) 216 } 217 err := p.model.ScanRepo(p.repoCfg.ID) 218 if err != nil { 219 invalidateRepo(p.cfg, p.repoCfg.ID, err) 220 return 221 } 222 } 223 } 224 225 func (p *puller) fixupDirectories() { 226 var deleteDirs []string 227 var changed = 0 228 229 var walkFn = func(path string, info os.FileInfo, err error) error { 230 if err != nil { 231 return err 232 } 233 234 if !info.IsDir() { 235 return nil 236 } 237 238 rn, err := filepath.Rel(p.repoCfg.Directory, path) 239 if err != nil { 240 return nil 241 } 242 243 if rn == "." { 244 return nil 245 } 246 247 if filepath.Base(rn) == ".stversions" { 248 return filepath.SkipDir 249 } 250 251 cur := p.model.CurrentRepoFile(p.repoCfg.ID, rn) 252 if cur.Name != rn { 253 // No matching dir in current list; weird 254 if debug { 255 l.Debugf("missing dir: %s; %v", rn, cur) 256 } 257 return nil 258 } 259 260 if protocol.IsDeleted(cur.Flags) { 261 if debug { 262 l.Debugf("queue delete dir: %v", cur) 263 } 264 265 // We queue the directories to delete since we walk the 266 // tree in depth first order and need to remove the 267 // directories in the opposite order. 268 269 deleteDirs = append(deleteDirs, path) 270 return nil 271 } 272 273 if !p.repoCfg.IgnorePerms && protocol.HasPermissionBits(cur.Flags) && !scanner.PermsEqual(cur.Flags, uint32(info.Mode())) { 274 err := os.Chmod(path, os.FileMode(cur.Flags)&os.ModePerm) 275 if err != nil { 276 l.Warnf("Restoring folder flags: %q: %v", path, err) 277 } else { 278 changed++ 279 if debug { 280 l.Debugf("restored dir flags: %o -> %v", info.Mode()&os.ModePerm, cur) 281 } 282 } 283 } 284 285 return nil 286 } 287 288 for { 289 deleteDirs = nil 290 changed = 0 291 filepath.Walk(p.repoCfg.Directory, walkFn) 292 293 var deleted = 0 294 // Delete any queued directories 295 for i := len(deleteDirs) - 1; i >= 0; i-- { 296 dir := deleteDirs[i] 297 if debug { 298 l.Debugln("delete dir:", dir) 299 } 300 err := os.Remove(dir) 301 if err == nil { 302 deleted++ 303 } else { 304 l.Warnln("Delete dir:", err) 305 } 306 } 307 308 if debug { 309 l.Debugf("changed %d, deleted %d dirs", changed, deleted) 310 } 311 312 if changed+deleted == 0 { 313 return 314 } 315 } 316 } 317 318 func (p *puller) handleRequestResult(res requestResult) { 319 p.oustandingPerNode.decrease(res.node) 320 f := res.file 321 322 of, ok := p.openFiles[f.Name] 323 if !ok { 324 // no entry in openFiles means there was an error and we've cancelled the operation 325 return 326 } 327 328 if res.err != nil { 329 // This request resulted in an error 330 of.err = res.err 331 if debug { 332 l.Debugf("pull: not writing %q / %q offset %d: %v; (done=%v, outstanding=%d)", p.repoCfg.ID, f.Name, res.offset, res.err, of.done, of.outstanding) 333 } 334 } else if of.err == nil { 335 // This request was sucessfull and nothing has failed previously either 336 _, of.err = of.file.WriteAt(res.data, res.offset) 337 if debug { 338 l.Debugf("pull: wrote %q / %q offset %d len %d outstanding %d done %v", p.repoCfg.ID, f.Name, res.offset, len(res.data), of.outstanding, of.done) 339 } 340 } 341 342 of.outstanding-- 343 p.openFiles[f.Name] = of 344 345 if of.done && of.outstanding == 0 { 346 p.closeFile(f) 347 } 348 } 349 350 // handleBlock fulfills the block request by copying, ignoring or fetching 351 // from the network. Returns true if the block was fully handled 352 // synchronously, i.e. if the slot can be reused. 353 func (p *puller) handleBlock(b bqBlock) bool { 354 f := b.file 355 356 // For directories, making sure they exist is enough. 357 // Deleted directories we mark as handled and delete later. 358 if protocol.IsDirectory(f.Flags) { 359 if !protocol.IsDeleted(f.Flags) { 360 path := filepath.Join(p.repoCfg.Directory, f.Name) 361 _, err := os.Stat(path) 362 if err != nil && os.IsNotExist(err) { 363 if debug { 364 l.Debugf("create dir: %v", f) 365 } 366 err = os.MkdirAll(path, os.FileMode(f.Flags&0777)) 367 if err != nil { 368 l.Warnf("Create folder: %q: %v", path, err) 369 } 370 } 371 } else if debug { 372 l.Debugf("ignore delete dir: %v", f) 373 } 374 p.model.updateLocal(p.repoCfg.ID, f) 375 return true 376 } 377 378 if len(b.copy) > 0 && len(b.copy) == len(b.file.Blocks) && b.last { 379 // We are supposed to copy the entire file, and then fetch nothing. 380 // We don't actually need to make the copy. 381 if debug { 382 l.Debugln("taking shortcut:", f) 383 } 384 fp := filepath.Join(p.repoCfg.Directory, f.Name) 385 t := time.Unix(f.Modified, 0) 386 err := os.Chtimes(fp, t, t) 387 if debug && err != nil { 388 l.Debugf("pull: error: %q / %q: %v", p.repoCfg.ID, f.Name, err) 389 } 390 if !p.repoCfg.IgnorePerms && protocol.HasPermissionBits(f.Flags) { 391 err = os.Chmod(fp, os.FileMode(f.Flags&0777)) 392 if debug && err != nil { 393 l.Debugf("pull: error: %q / %q: %v", p.repoCfg.ID, f.Name, err) 394 } 395 } 396 397 events.Default.Log(events.ItemStarted, map[string]string{ 398 "repo": p.repoCfg.ID, 399 "item": f.Name, 400 }) 401 402 p.model.updateLocal(p.repoCfg.ID, f) 403 return true 404 } 405 406 of, ok := p.openFiles[f.Name] 407 of.done = b.last 408 409 if !ok { 410 if debug { 411 l.Debugf("pull: %q: opening file %q", p.repoCfg.ID, f.Name) 412 } 413 414 events.Default.Log(events.ItemStarted, map[string]string{ 415 "repo": p.repoCfg.ID, 416 "item": f.Name, 417 }) 418 419 of.availability = p.model.repoFiles[p.repoCfg.ID].Availability(f.Name) 420 of.filepath = filepath.Join(p.repoCfg.Directory, f.Name) 421 of.temp = filepath.Join(p.repoCfg.Directory, defTempNamer.TempName(f.Name)) 422 423 dirName := filepath.Dir(of.filepath) 424 _, err := os.Stat(dirName) 425 if err != nil { 426 err = os.MkdirAll(dirName, 0777) 427 } 428 if err != nil { 429 l.Debugf("pull: error: %q / %q: %v", p.repoCfg.ID, f.Name, err) 430 } 431 432 of.file, of.err = os.Create(of.temp) 433 if of.err != nil { 434 if debug { 435 l.Debugf("pull: error: %q / %q: %v", p.repoCfg.ID, f.Name, of.err) 436 } 437 if !b.last { 438 p.openFiles[f.Name] = of 439 } 440 return true 441 } 442 osutil.HideFile(of.temp) 443 } 444 445 if of.err != nil { 446 // We have already failed this file. 447 if debug { 448 l.Debugf("pull: error: %q / %q has already failed: %v", p.repoCfg.ID, f.Name, of.err) 449 } 450 if b.last { 451 delete(p.openFiles, f.Name) 452 } 453 454 return true 455 } 456 457 p.openFiles[f.Name] = of 458 459 switch { 460 case len(b.copy) > 0: 461 p.handleCopyBlock(b) 462 return true 463 464 case b.block.Size > 0: 465 return p.handleRequestBlock(b) 466 467 default: 468 p.handleEmptyBlock(b) 469 return true 470 } 471 } 472 473 func (p *puller) handleCopyBlock(b bqBlock) { 474 // We have blocks to copy from the existing file 475 f := b.file 476 of := p.openFiles[f.Name] 477 478 if debug { 479 l.Debugf("pull: copying %d blocks for %q / %q", len(b.copy), p.repoCfg.ID, f.Name) 480 } 481 482 var exfd *os.File 483 exfd, of.err = os.Open(of.filepath) 484 if of.err != nil { 485 if debug { 486 l.Debugf("pull: error: %q / %q: %v", p.repoCfg.ID, f.Name, of.err) 487 } 488 of.file.Close() 489 of.file = nil 490 491 p.openFiles[f.Name] = of 492 return 493 } 494 defer exfd.Close() 495 496 for _, b := range b.copy { 497 bs := make([]byte, b.Size) 498 _, of.err = exfd.ReadAt(bs, b.Offset) 499 if of.err == nil { 500 _, of.err = of.file.WriteAt(bs, b.Offset) 501 } 502 if of.err != nil { 503 if debug { 504 l.Debugf("pull: error: %q / %q: %v", p.repoCfg.ID, f.Name, of.err) 505 } 506 exfd.Close() 507 of.file.Close() 508 of.file = nil 509 510 p.openFiles[f.Name] = of 511 return 512 } 513 } 514 } 515 516 // handleRequestBlock tries to pull a block from the network. Returns true if 517 // the block could _not_ be fetched (i.e. it was fully handled, matching the 518 // return criteria of handleBlock) 519 func (p *puller) handleRequestBlock(b bqBlock) bool { 520 f := b.file 521 of, ok := p.openFiles[f.Name] 522 if !ok { 523 panic("bug: request for non-open file") 524 } 525 526 node := p.oustandingPerNode.leastBusyNode(of.availability, p.model.ConnectedTo) 527 if node == (protocol.NodeID{}) { 528 of.err = errNoNode 529 if of.file != nil { 530 of.file.Close() 531 of.file = nil 532 os.Remove(of.temp) 533 if debug { 534 l.Debugf("pull: no source for %q / %q; closed", p.repoCfg.ID, f.Name) 535 } 536 } 537 if b.last { 538 if debug { 539 l.Debugf("pull: no source for %q / %q; deleting", p.repoCfg.ID, f.Name) 540 } 541 delete(p.openFiles, f.Name) 542 } else { 543 if debug { 544 l.Debugf("pull: no source for %q / %q; await more blocks", p.repoCfg.ID, f.Name) 545 } 546 p.openFiles[f.Name] = of 547 } 548 return true 549 } 550 551 of.outstanding++ 552 p.openFiles[f.Name] = of 553 554 go func(node protocol.NodeID, b bqBlock) { 555 if debug { 556 l.Debugf("pull: requesting %q / %q offset %d size %d from %q outstanding %d", p.repoCfg.ID, f.Name, b.block.Offset, b.block.Size, node, of.outstanding) 557 } 558 559 bs, err := p.model.requestGlobal(node, p.repoCfg.ID, f.Name, b.block.Offset, int(b.block.Size), nil) 560 p.requestResults <- requestResult{ 561 node: node, 562 file: f, 563 filepath: of.filepath, 564 offset: b.block.Offset, 565 data: bs, 566 err: err, 567 } 568 }(node, b) 569 570 return false 571 } 572 573 func (p *puller) handleEmptyBlock(b bqBlock) { 574 f := b.file 575 of := p.openFiles[f.Name] 576 577 if b.last { 578 if of.err == nil { 579 of.file.Close() 580 } 581 } 582 583 if protocol.IsDeleted(f.Flags) { 584 if debug { 585 l.Debugf("pull: delete %q", f.Name) 586 } 587 os.Remove(of.temp) 588 os.Chmod(of.filepath, 0666) 589 if p.versioner != nil { 590 if debug { 591 l.Debugln("pull: deleting with versioner") 592 } 593 if err := p.versioner.Archive(p.repoCfg.Directory, of.filepath); err == nil { 594 p.model.updateLocal(p.repoCfg.ID, f) 595 } else if debug { 596 l.Debugln("pull: error:", err) 597 } 598 } else if err := os.Remove(of.filepath); err == nil || os.IsNotExist(err) { 599 p.model.updateLocal(p.repoCfg.ID, f) 600 } 601 } else { 602 if debug { 603 l.Debugf("pull: no blocks to fetch and nothing to copy for %q / %q", p.repoCfg.ID, f.Name) 604 } 605 t := time.Unix(f.Modified, 0) 606 if os.Chtimes(of.temp, t, t) != nil { 607 delete(p.openFiles, f.Name) 608 return 609 } 610 if !p.repoCfg.IgnorePerms && protocol.HasPermissionBits(f.Flags) && os.Chmod(of.temp, os.FileMode(f.Flags&0777)) != nil { 611 delete(p.openFiles, f.Name) 612 return 613 } 614 osutil.ShowFile(of.temp) 615 if osutil.Rename(of.temp, of.filepath) == nil { 616 p.model.updateLocal(p.repoCfg.ID, f) 617 } 618 } 619 delete(p.openFiles, f.Name) 620 } 621 622 func (p *puller) queueNeededBlocks(prevVer uint64) (uint64, int) { 623 curVer := p.model.LocalVersion(p.repoCfg.ID) 624 if curVer == prevVer { 625 return curVer, 0 626 } 627 628 if debug { 629 l.Debugf("%q: checking for more needed blocks", p.repoCfg.ID) 630 } 631 632 queued := 0 633 for _, f := range p.model.NeedFilesRepo(p.repoCfg.ID) { 634 if _, ok := p.openFiles[f.Name]; ok { 635 continue 636 } 637 lf := p.model.CurrentRepoFile(p.repoCfg.ID, f.Name) 638 have, need := scanner.BlockDiff(lf.Blocks, f.Blocks) 639 if debug { 640 l.Debugf("need:\n local: %v\n global: %v\n haveBlocks: %v\n needBlocks: %v", lf, f, have, need) 641 } 642 queued++ 643 p.bq.put(bqAdd{ 644 file: f, 645 have: have, 646 need: need, 647 }) 648 } 649 if debug && queued > 0 { 650 l.Debugf("%q: queued %d items", p.repoCfg.ID, queued) 651 } 652 653 if queued > 0 { 654 return prevVer, queued 655 } else { 656 return curVer, 0 657 } 658 } 659 660 func (p *puller) closeFile(f protocol.FileInfo) { 661 if debug { 662 l.Debugf("pull: closing %q / %q", p.repoCfg.ID, f.Name) 663 } 664 665 of := p.openFiles[f.Name] 666 of.file.Close() 667 defer os.Remove(of.temp) 668 669 delete(p.openFiles, f.Name) 670 671 fd, err := os.Open(of.temp) 672 if err != nil { 673 if debug { 674 l.Debugf("pull: error: %q / %q: %v", p.repoCfg.ID, f.Name, err) 675 } 676 return 677 } 678 hb, _ := scanner.Blocks(fd, scanner.StandardBlockSize) 679 fd.Close() 680 681 if l0, l1 := len(hb), len(f.Blocks); l0 != l1 { 682 if debug { 683 l.Debugf("pull: %q / %q: nblocks %d != %d", p.repoCfg.ID, f.Name, l0, l1) 684 } 685 return 686 } 687 688 for i := range hb { 689 if bytes.Compare(hb[i].Hash, f.Blocks[i].Hash) != 0 { 690 l.Debugf("pull: %q / %q: block %d hash mismatch\n\thave: %x\n\twant: %x", p.repoCfg.ID, f.Name, i, hb[i].Hash, f.Blocks[i].Hash) 691 return 692 } 693 } 694 695 t := time.Unix(f.Modified, 0) 696 err = os.Chtimes(of.temp, t, t) 697 if debug && err != nil { 698 l.Debugf("pull: error: %q / %q: %v", p.repoCfg.ID, f.Name, err) 699 } 700 if !p.repoCfg.IgnorePerms && protocol.HasPermissionBits(f.Flags) { 701 err = os.Chmod(of.temp, os.FileMode(f.Flags&0777)) 702 if debug && err != nil { 703 l.Debugf("pull: error: %q / %q: %v", p.repoCfg.ID, f.Name, err) 704 } 705 } 706 707 osutil.ShowFile(of.temp) 708 709 if p.versioner != nil { 710 err := p.versioner.Archive(p.repoCfg.Directory, of.filepath) 711 if err != nil { 712 if debug { 713 l.Debugf("pull: error: %q / %q: %v", p.repoCfg.ID, f.Name, err) 714 } 715 return 716 } 717 } 718 719 if debug { 720 l.Debugf("pull: rename %q / %q: %q", p.repoCfg.ID, f.Name, of.filepath) 721 } 722 if err := osutil.Rename(of.temp, of.filepath); err == nil { 723 p.model.updateLocal(p.repoCfg.ID, f) 724 } else { 725 l.Debugf("pull: error: %q / %q: %v", p.repoCfg.ID, f.Name, err) 726 } 727 } 728 729 func invalidateRepo(cfg *config.Configuration, repoID string, err error) { 730 for i := range cfg.Repositories { 731 repo := &cfg.Repositories[i] 732 if repo.ID == repoID { 733 repo.Invalid = err.Error() 734 return 735 } 736 } 737 }