commit 3b5281a4191efd5336328932fbe74e17e9eb7614
Author: Yawning Angel <[email protected]>
Date:   Fri Dec 23 03:02:16 2016 +0000

    Bug 20851: If the incremental update fails, fall back to the complete 
update.
---
 ChangeLog                                          |   2 +
 .../internal/ui/config/config.go                   |  12 ++
 .../sandboxed-tor-browser/internal/ui/launch.go    |   6 +-
 .../sandboxed-tor-browser/internal/ui/update.go    | 226 +++++++++++++++------
 4 files changed, 175 insertions(+), 71 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index c7d7e4a..5344f14 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,6 @@
 Changes in version 0.0.3 - UNRELEASED:
+ * Bug 20851: If the incremental update fails, fall back to the complete
+   update.
  * Bug 21055: Fall back gracefully if the Adwaita theme is not present.
  * Bug 20791: Fetch install/update metadata using onions.
  * Bug 20979: runtime/cgo: pthread_create failed: Resource temporarily
diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go 
b/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go
index a1af1cd..9ffbd5c 100644
--- a/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go
+++ b/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go
@@ -302,6 +302,9 @@ type Config struct {
        // ForceUpdate is set if the installed bundle is known to be obsolete.
        ForceUpdate bool `json:"forceUpdate"`
 
+       // SkipPartialUpdate is set if the partial update has failed to apply.
+       SkipPartialUpdate bool `json:"skipPartialUpdate"`
+
        // Tor is the Tor network configuration.
        Tor Tor `json:"tor,omitEmpty"`
 
@@ -381,6 +384,15 @@ func (cfg *Config) SetForceUpdate(b bool) {
        }
 }
 
+// SetSkipPartailUpdate sets the bundle as needing a complete update as opposed
+// to a partial one, and marks the config dirty.
+func (cfg *Config) SetSkipPartialUpdate(b bool) {
+       if cfg.SkipPartialUpdate != b {
+               cfg.SkipPartialUpdate = b
+               cfg.isDirty = true
+       }
+}
+
 // Sanitize validates the config, and brings it inline with reality.
 func (cfg *Config) Sanitize() {
        if !utils.DirExists(cfg.Sandbox.DownloadsDir) {
diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/launch.go 
b/src/cmd/sandboxed-tor-browser/internal/ui/launch.go
index 6a86dba..49b7663 100644
--- a/src/cmd/sandboxed-tor-browser/internal/ui/launch.go
+++ b/src/cmd/sandboxed-tor-browser/internal/ui/launch.go
@@ -63,15 +63,13 @@ func (c *Common) DoLaunch(async *Async, checkUpdates bool) {
        // Start tor if required.
        log.Printf("launch: Connecting to the Tor network.")
        async.UpdateProgress("Connecting to the Tor network.")
-       dialFn, err := c.launchTor(async, false)
-       if err != nil {
-               async.Err = err
+       if _, async.Err = c.launchTor(async, false); async.Err != nil {
                return
        }
 
        // If an update check is needed, check for updates.
        if checkUpdates {
-               c.doUpdate(async, dialFn)
+               c.doUpdate(async)
                if async.Err != nil {
                        return
                }
diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/update.go 
b/src/cmd/sandboxed-tor-browser/internal/ui/update.go
index 7c5f812..1994e64 100644
--- a/src/cmd/sandboxed-tor-browser/internal/ui/update.go
+++ b/src/cmd/sandboxed-tor-browser/internal/ui/update.go
@@ -25,16 +25,29 @@ import (
 
        "cmd/sandboxed-tor-browser/internal/installer"
        "cmd/sandboxed-tor-browser/internal/sandbox"
+       "cmd/sandboxed-tor-browser/internal/tor"
        . "cmd/sandboxed-tor-browser/internal/ui/async"
 )
 
-func (c *Common) CheckUpdate(async *Async, dialFn dialFunc) 
*installer.UpdateEntry {
+// CheckUpdate queries the update server to see if an update for the current
+// bundle is available.
+func (c *Common) CheckUpdate(async *Async) *installer.UpdateEntry {
        // Check for updates.
        log.Printf("update: Checking for updates.")
        async.UpdateProgress("Checking for updates.")
 
        // Create the async HTTP client.
-       client := newHPKPGrabClient(dialFn)
+       if c.tor == nil {
+               async.Err = tor.ErrTorNotRunning
+               return nil
+       }
+       dialer, err := c.tor.Dialer()
+       if err != nil {
+               async.Err = err
+               return nil
+       }
+
+       client := newHPKPGrabClient(dialer.Dial)
 
        // Determine where the update metadata should be fetched from.
        updateURLs := []string{}
@@ -57,7 +70,9 @@ func (c *Common) CheckUpdate(async *Async, dialFn dialFunc) 
*installer.UpdateEnt
        for _, url := range updateURLs {
                log.Printf("update: Metadata URL: %v", url)
                async.Err = nil // Clear errors per fetch.
-               if b := async.Grab(client, url, nil); async.Err != nil {
+               if b := async.Grab(client, url, nil); async.Err == ErrCanceled {
+                       return nil
+               } else if async.Err != nil {
                        log.Printf("update: Metadata download failed: %v", 
async.Err)
                        continue
                } else if update, async.Err = installer.GetUpdateEntry(b); 
async.Err != nil {
@@ -69,13 +84,14 @@ func (c *Common) CheckUpdate(async *Async, dialFn dialFunc) 
*installer.UpdateEnt
        }
 
        if !fetchOk {
-               // This should be set from the last update attempt...
-               if async.Err == nil {
-                       async.Err = fmt.Errorf("failed to download update 
metadata")
-               }
+               // The last update attempt likely isn't the only relevant error,
+               // just set this to something that won't terrify users, more 
detailed
+               // diagnostics are avaialble in the log.
+               async.Err = fmt.Errorf("failed to download update metadata")
                return nil
        }
 
+       // If there is an update, tag the installed bundle as stale...
        if update == nil {
                log.Printf("update: Installed bundle is current.")
                c.Cfg.SetForceUpdate(false)
@@ -84,6 +100,7 @@ func (c *Common) CheckUpdate(async *Async, dialFn dialFunc) 
*installer.UpdateEnt
                c.Cfg.SetForceUpdate(true)
        }
 
+       // ... and flush the config.
        if async.Err = c.Cfg.Sync(); async.Err != nil {
                return nil
        }
@@ -91,38 +108,26 @@ func (c *Common) CheckUpdate(async *Async, dialFn 
dialFunc) *installer.UpdateEnt
        return update
 }
 
-func (c *Common) doUpdate(async *Async, dialFn dialFunc) {
-       // This attempts to follow the process that Firefox uses to check for
-       // updates.  
https://wiki.mozilla.org/Software_Update:Checking_For_Updates
+// FetchUpdate downloads the update specified by the patch over tor, and
+// validates it with the hash in the patch datastructure, and the known MAR
+// signing keys.
+func (c *Common) FetchUpdate(async *Async, patch *installer.Patch) []byte {
+       var dialFn dialFunc
 
-       // Check for updates.
-       update := c.CheckUpdate(async, dialFn)
-       if async.Err != nil || update == nil {
-               return
-       }
-
-       // Ensure that the update entry version is actually neweer.
-       if !c.Manif.BundleUpdateVersionValid(update.AppVersion) {
-               log.Printf("update: Update server provided a downgrade: '%v'", 
update.AppVersion)
-               async.Err = fmt.Errorf("update server provided a downgrade: 
'%v'", update.AppVersion)
-               return
-       }
-
-       // Figure out the best MAR to download.
-       patches := make(map[string]*installer.Patch)
-       for _, v := range update.Patch {
-               if patches[v.Type] != nil {
-                       async.Err = fmt.Errorf("duplicate patch entry for kind: 
'%v'", v.Type)
-                       return
+       // Launch the tor daemon if needed.
+       if c.tor == nil {
+               dialFn, async.Err = c.launchTor(async, false)
+               if async.Err != nil {
+                       return nil
                }
-               patches[v.Type] = &v
-       }
-       patch := patches["partial"] // Favor the delta update mechanism.
-       if patch == nil {
-               if patch = patches["complete"]; patch == nil {
-                       async.Err = fmt.Errorf("no suitable MAR file found")
-                       return
+       } else {
+               // Otherwise, retreive the dialer.
+               dialer, err := c.tor.Dialer()
+               if err != nil {
+                       async.Err = err
+                       return nil
                }
+               dialFn = dialer.Dial
        }
 
        // Download the MAR file.
@@ -132,7 +137,7 @@ func (c *Common) doUpdate(async *Async, dialFn dialFunc) {
        var mar []byte
        client := newHPKPGrabClient(dialFn)
        if mar = async.Grab(client, patch.Url, func(s string) { 
async.UpdateProgress(fmt.Sprintf("Downloading Tor Browser Update: %s", s)) }); 
async.Err != nil {
-               return
+               return nil
        }
 
        log.Printf("update: Validating Tor Browser Update.")
@@ -142,64 +147,151 @@ func (c *Common) doUpdate(async *Async, dialFn dialFunc) 
{
        expectedHash, err := hex.DecodeString(patch.HashValue)
        if err != nil {
                async.Err = fmt.Errorf("failed to decode HashValue: %v", err)
-               return
+               return nil
        }
        switch patch.HashFunction {
        case "SHA512":
                derivedHash := sha512.Sum512(mar)
                if !bytes.Equal(expectedHash, derivedHash[:]) {
                        async.Err = fmt.Errorf("downloaded hash does not match 
patch metadata")
-                       return
+                       return nil
                }
        default:
                async.Err = fmt.Errorf("unsupported hash function: '%v'", 
patch.HashFunction)
-               return
+               return nil
        }
 
        // ... and verify the signature block in the MAR with our copy of the 
key.
        if async.Err = installer.VerifyTorBrowserMAR(mar); async.Err != nil {
-               return
+               return nil
        }
 
-       // Shutdown the old tor now.
-       if c.tor != nil {
-               log.Printf("update: Shutting down old tor.")
-               c.tor.Shutdown()
-               c.tor = nil
-       }
+       return mar
+}
 
-       // Apply the update.
-       log.Printf("update: Updating Tor Browser.")
-       async.UpdateProgress("Updating Tor Browser.")
+func (c *Common) doUpdate(async *Async) {
+       // This attempts to follow the process that Firefox uses to check for
+       // updates.  
https://wiki.mozilla.org/Software_Update:Checking_For_Updates
 
-       async.ToUI <- false //  Lock out canceling.
+       const (
+               patchPartial  = "partial"
+               patchComplete = "complete"
+       )
 
-       if async.Err = sandbox.RunUpdate(c.Cfg, mar); async.Err != nil {
+       // Check for updates.
+       update := c.CheckUpdate(async)
+       if async.Err != nil || update == nil {
+               // Something either broke, or the bundle is up to date.  The 
caller
+               // needs to check async.Err, and either way there's nothing 
more that
+               // can be done.
                return
        }
 
-       // Reinstall the autoconfig stuff.
-       if async.Err = writeAutoconfig(c.Cfg); async.Err != nil {
+       // Ensure that the update entry version is actually neweer.
+       if !c.Manif.BundleUpdateVersionValid(update.AppVersion) {
+               log.Printf("update: Update server provided a downgrade: '%v'", 
update.AppVersion)
+               async.Err = fmt.Errorf("update server provided a downgrade: 
'%v'", update.AppVersion)
                return
        }
 
-       // Update the maniftest and config.
-       c.Manif.SetVersion(update.AppVersion)
-       if async.Err = c.Manif.Sync(); async.Err != nil {
-               return
+       // Figure out the best MAR to download.
+       patches := make(map[string]*installer.Patch)
+       for i := 0; i < len(update.Patch); i++ {
+               v := &update.Patch[i]
+               if patches[v.Type] != nil {
+                       async.Err = fmt.Errorf("duplicate patch entry for kind: 
'%v'", v.Type)
+                       return
+               }
+               patches[v.Type] = v
        }
-       c.Cfg.SetForceUpdate(false)
-       if async.Err = c.Cfg.Sync(); async.Err != nil {
-               return
+
+       patchTypes := []string{}
+       if !c.Cfg.SkipPartialUpdate {
+               patchTypes = append(patchTypes, patchPartial)
        }
+       patchTypes = append(patchTypes, patchComplete)
+
+       // Cycle through the patch types, and apply the "best" one.
+       nrAttempts := 0
+       for _, patchType := range patchTypes {
+               async.Err = nil
+
+               patch := patches[patchType]
+               if patch == nil {
+                       continue
+               }
+
+               nrAttempts++
+               mar := c.FetchUpdate(async, patch)
+               if async.Err == ErrCanceled {
+                       return
+               } else if async.Err != nil {
+                       log.Printf("update: Failed to fetch update: %v", 
async.Err)
+                       continue
+               }
+               if mar == nil {
+                       panic("update: no MAR returned from successful fetch")
+               }
+
+               // Shutdown the old tor now.
+               if c.tor != nil {
+                       log.Printf("update: Shutting down old tor.")
+                       c.tor.Shutdown()
+                       c.tor = nil
+               }
+
+               // Apply the update.
+               log.Printf("update: Updating Tor Browser.")
+               async.UpdateProgress("Updating Tor Browser.")
+
+               async.ToUI <- false //  Lock out canceling.
+
+               if async.Err = sandbox.RunUpdate(c.Cfg, mar); async.Err != nil {
+                       log.Printf("update: Failed to apply update: %v", 
async.Err)
+                       if patchType == patchPartial {
+                               c.Cfg.SetSkipPartialUpdate(true)
+                               if async.Err = c.Cfg.Sync(); async.Err != nil {
+                                       return
+                               }
+                       }
+                       async.ToUI <- true // Unlock canceling.
+                       continue
+               }
+
+               // Failues past this point are catastrophic in that, the on-disk
+               // bundle is up to date, but the post-update tasks have failed.
+
+               // Reinstall the autoconfig stuff.
+               if async.Err = writeAutoconfig(c.Cfg); async.Err != nil {
+                       return
+               }
 
-       async.ToUI <- true // Unlock canceling.
+               // Update the maniftest and config.
+               c.Manif.SetVersion(update.AppVersion)
+               if async.Err = c.Manif.Sync(); async.Err != nil {
+                       return
+               }
+               c.Cfg.SetForceUpdate(false)
+               c.Cfg.SetSkipPartialUpdate(false)
+               if async.Err = c.Cfg.Sync(); async.Err != nil {
+                       return
+               }
+
+               async.ToUI <- true // Unlock canceling.
+
+               // Restart tor if we launched it.
+               if !c.Cfg.UseSystemTor {
+                       log.Printf("launch: Reconnecting to the Tor network.")
+                       async.UpdateProgress("Reconnecting to the Tor network.")
+                       _, async.Err = c.launchTor(async, false)
+               }
+               return
+       }
 
-       // Restart tor if we launched it.
-       if !c.Cfg.UseSystemTor {
-               log.Printf("launch: Reconnecting to the Tor network.")
-               async.UpdateProgress("Reconnecting to the Tor network.")
-               _, async.Err = c.launchTor(async, false)
+       if nrAttempts == 0 {
+               async.Err = fmt.Errorf("no suitable MAR file found")
+       } else if async.Err != ErrCanceled {
+               async.Err = fmt.Errorf("failed to apply all possible MAR files")
        }
        return
 }



_______________________________________________
tor-commits mailing list
[email protected]
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits

Reply via email to