| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- // Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
- //
- // This program is free software: you can redistribute it and/or modify it
- // under the terms of the GNU General Public License as published by the Free
- // Software Foundation, either version 3 of the License, or (at your option)
- // any later version.
- //
- // This program is distributed in the hope that it will be useful, but WITHOUT
- // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- // more details.
- //
- // You should have received a copy of the GNU General Public License along
- // with this program. If not, see <http://www.gnu.org/licenses/>.
- package model
- import (
- "os"
- "path/filepath"
- "sync"
- "github.com/syncthing/syncthing/internal/osutil"
- "github.com/syncthing/syncthing/internal/protocol"
- )
- // A sharedPullerState is kept for each file that is being synced and is kept
- // updated along the way.
- type sharedPullerState struct {
- // Immutable, does not require locking
- file protocol.FileInfo
- folder string
- tempName string
- realName string
- reused int // Number of blocks reused from temporary file
- // Mutable, must be locked for access
- err error // The first error we hit
- fd *os.File // The fd of the temp file
- copyTotal int // Total number of copy actions for the whole job
- pullTotal int // Total number of pull actions for the whole job
- copyNeeded int // Number of copy actions still pending
- pullNeeded int // Number of block pulls still pending
- copyOrigin int // Number of blocks copied from the original file
- closed bool // Set when the file has been closed
- mut sync.Mutex // Protects the above
- }
- // tempFile returns the fd for the temporary file, reusing an open fd
- // or creating the file as necessary.
- func (s *sharedPullerState) tempFile() (*os.File, error) {
- s.mut.Lock()
- defer s.mut.Unlock()
- // If we've already hit an error, return early
- if s.err != nil {
- return nil, s.err
- }
- // If the temp file is already open, return the file descriptor
- if s.fd != nil {
- return s.fd, nil
- }
- // Ensure that the parent directory is writable. This is
- // osutil.InWritableDir except we need to do more stuff so we duplicate it
- // here.
- dir := filepath.Dir(s.tempName)
- if info, err := os.Stat(dir); err != nil {
- s.earlyCloseLocked("dst stat dir", err)
- return nil, err
- } else if info.Mode()&0200 == 0 {
- err := os.Chmod(dir, 0755)
- if err == nil {
- defer func() {
- err := os.Chmod(dir, info.Mode().Perm())
- if err != nil {
- panic(err)
- }
- }()
- }
- }
- // Attempt to create the temp file
- flags := os.O_WRONLY
- if s.reused == 0 {
- flags |= os.O_CREATE | os.O_EXCL
- }
- fd, err := os.OpenFile(s.tempName, flags, 0644)
- if err != nil {
- s.earlyCloseLocked("dst create", err)
- return nil, err
- }
- // Same fd will be used by all writers
- s.fd = fd
- return fd, nil
- }
- // sourceFile opens the existing source file for reading
- func (s *sharedPullerState) sourceFile() (*os.File, error) {
- s.mut.Lock()
- defer s.mut.Unlock()
- // If we've already hit an error, return early
- if s.err != nil {
- return nil, s.err
- }
- // Attempt to open the existing file
- fd, err := os.Open(s.realName)
- if err != nil {
- s.earlyCloseLocked("src open", err)
- return nil, err
- }
- return fd, nil
- }
- // earlyClose prints a warning message composed of the context and
- // error, and marks the sharedPullerState as failed. Is a no-op when called on
- // an already failed state.
- func (s *sharedPullerState) earlyClose(context string, err error) {
- s.mut.Lock()
- defer s.mut.Unlock()
- s.earlyCloseLocked(context, err)
- }
- func (s *sharedPullerState) earlyCloseLocked(context string, err error) {
- if s.err != nil {
- return
- }
- l.Infof("Puller (folder %q, file %q): %s: %v", s.folder, s.file.Name, context, err)
- s.err = err
- if s.fd != nil {
- s.fd.Close()
- // Delete temporary file, even if parent dir is read-only
- osutil.InWritableDir(func(string) error { os.Remove(s.tempName); return nil }, s.tempName)
- }
- s.closed = true
- }
- func (s *sharedPullerState) failed() error {
- s.mut.Lock()
- defer s.mut.Unlock()
- return s.err
- }
- func (s *sharedPullerState) copyDone() {
- s.mut.Lock()
- s.copyNeeded--
- if debug {
- l.Debugln("sharedPullerState", s.folder, s.file.Name, "copyNeeded ->", s.copyNeeded)
- }
- s.mut.Unlock()
- }
- func (s *sharedPullerState) copiedFromOrigin() {
- s.mut.Lock()
- s.copyOrigin++
- s.mut.Unlock()
- }
- func (s *sharedPullerState) pullStarted() {
- s.mut.Lock()
- s.copyTotal--
- s.copyNeeded--
- s.pullTotal++
- s.pullNeeded++
- if debug {
- l.Debugln("sharedPullerState", s.folder, s.file.Name, "pullNeeded start ->", s.pullNeeded)
- }
- s.mut.Unlock()
- }
- func (s *sharedPullerState) pullDone() {
- s.mut.Lock()
- s.pullNeeded--
- if debug {
- l.Debugln("sharedPullerState", s.folder, s.file.Name, "pullNeeded done ->", s.pullNeeded)
- }
- s.mut.Unlock()
- }
- // finalClose atomically closes and returns closed status of a file. A true
- // first return value means the file was closed and should be finished, with
- // the error indicating the success or failure of the close. A false first
- // return value indicates the file is not ready to be closed, or is already
- // closed and should in either case not be finished off now.
- func (s *sharedPullerState) finalClose() (bool, error) {
- s.mut.Lock()
- defer s.mut.Unlock()
- if s.pullNeeded+s.copyNeeded != 0 {
- // Not done yet.
- return false, nil
- }
- if s.closed {
- // Already handled.
- return false, nil
- }
- s.closed = true
- if fd := s.fd; fd != nil {
- s.fd = nil
- return true, fd.Close()
- }
- return true, nil
- }
|