diff --git a/database/updates.inc b/database/updates.inc
index 9847ee136452c41095b598c9d8b8b98ddba47cb4..bbbade5ec9866b230181870d1bd417a347bc8b94 100644
--- a/database/updates.inc
+++ b/database/updates.inc
@@ -1458,8 +1458,9 @@ function system_update_169() {
     }
   }
 
+  // Note: 'access' table manually updated in update.php
   return _system_update_utf8(array(
-    'access', 'accesslog', 'aggregator_category',
+    'accesslog', 'aggregator_category',
     'aggregator_category_feed', 'aggregator_category_item',
     'aggregator_feed', 'aggregator_item', 'authmap', 'blocks',
     'book', 'boxes', 'cache', 'comments', 'contact',
@@ -1477,7 +1478,7 @@ function system_update_169() {
 }
 
 /**
- * Converts tables to UTF-8 encoding.
+ * Converts a set of tables to UTF-8 encoding.
  *
  * This update is designed to be re-usable by contrib modules and is
  * used by system_update_169().
@@ -1513,51 +1514,11 @@ function _system_update_utf8($tables) {
     $_SESSION['update_utf8_total'] = count($tables);
   }
 
-  // Fetch remaining tables list
+  // Fetch remaining tables list and convert next table
   $list = &$_SESSION['update_utf8'];
-  $ret = array();
-
-  // Convert a table to UTF-8.
-  // We change all text columns to their correspending binary type,
-  // then back to text, but with a UTF-8 character set.
-  // See: http://dev.mysql.com/doc/refman/4.1/en/charset-conversion.html
-  $types = array('char' => 'binary',
-                 'varchar' => 'varbinary',
-                 'tinytext' => 'tinyblob',
-                 'text' => 'blob',
-                 'mediumtext' => 'mediumblob',
-                 'longtext' => 'longblob');
-
-  // Get next table in list
-  $table = array_shift($list);
-  $convert_to_binary = array();
-  $convert_to_utf8 = array();
-
-  // Set table default charset
-  $ret[] = update_sql('ALTER TABLE {'. $table .'} DEFAULT CHARACTER SET utf8');
-
-  // Find out which columns need converting and build SQL statements
-  $result = db_query('SHOW FULL COLUMNS FROM {'. $table .'}');
-  while ($column = db_fetch_array($result)) {
-    list($type) = explode('(', $column['Type']);
-    if (isset($types[$type])) {
-      $names = 'CHANGE `'. $column['Field'] .'` `'. $column['Field'] .'` ';
-      $attributes = ' DEFAULT '. ($column['Default'] == 'NULL' ? 'NULL ' :
-                     "'". db_escape_string($column['Default']) ."' ") .
-                    ($column['Null'] == 'YES' ? 'NULL' : 'NOT NULL');
-
-      $convert_to_binary[] = $names . preg_replace('/'. $type .'/i', $types[$type], $column['Type']) . $attributes;
-      $convert_to_utf8[] = $names . $column['Type'] .' CHARACTER SET utf8'. $attributes;
-    }
-  }
-
-  if (count($convert_to_binary)) {
-    // Convert text columns to binary
-    $ret[] = update_sql('ALTER TABLE {'. $table .'} '. implode(', ', $convert_to_binary));
-    // Convert binary columns to UTF-8
-    $ret[] = update_sql('ALTER TABLE {'. $table .'} '. implode(', ', $convert_to_utf8));
-  }
 
+  $ret = update_convert_table_utf8(array_shift($list));
+  
   // Are we done?
   if (count($list) == 0) {
     unset($_SESSION['update_utf8']);
diff --git a/misc/progress.js b/misc/progress.js
index 84979d68fc06eeb887296df55e5ddce6206cb9b5..776e2b637082e16d70dfd756055b8697a3c270c1 100644
--- a/misc/progress.js
+++ b/misc/progress.js
@@ -8,11 +8,12 @@
  * e.g. pb = new progressBar('myProgressBar');
  *      some_element.appendChild(pb.element);
  */
-function progressBar(id, callback, method) {
+function progressBar(id, updateCallback, method, errorCallback) {
   var pb = this;
   this.id = id;
   this.method = method ? method : HTTPGet;
-  this.callback = callback;
+  this.updateCallback = updateCallback;
+  this.errorCallback = errorCallback;
 
   this.element = document.createElement('div');
   this.element.id = id;
@@ -41,8 +42,8 @@ progressBar.prototype.setProgress = function (percentage, message) {
       divs[i].innerHTML = message;
     }
   }
-  if (this.callback) {
-    this.callback(percentage, message, this);
+  if (this.updateCallback) {
+    this.updateCallback(percentage, message, this);
   }
 }
 
@@ -82,13 +83,13 @@ progressBar.prototype.sendPing = function () {
  */
 progressBar.prototype.receivePing = function (string, xmlhttp, pb) {
   if (xmlhttp.status != 200) {
-    return alert('An HTTP error '+ xmlhttp.status +' occured.\n'+ pb.uri);
+    return pb.displayError('An HTTP error '+ xmlhttp.status +' occured.\n'+ pb.uri);
   }
   // Parse response
   var progress = parseJson(string);
   // Display errors
   if (progress.status == 0) {
-    alert(progress.data);
+    pb.displayError(progress.data);
     return;
   }
 
@@ -97,3 +98,19 @@ progressBar.prototype.receivePing = function (string, xmlhttp, pb) {
   // Schedule next timer
   pb.timer = setTimeout(function() { pb.sendPing(); }, pb.delay);
 }
+
+/**
+ * Display errors on the page.
+ */
+progressBar.prototype.displayError = function (string) {
+  var error = document.createElement('div');
+  error.className = 'error';
+  error.appendChild(document.createTextNode(string));
+
+  this.element.style.display = 'none';
+  this.element.parentNode.insertBefore(error, this.element);
+
+  if (this.errorCallback) {
+    this.errorCallback(this);
+  }
+}
diff --git a/misc/update.js b/misc/update.js
index 2586fbf505c2bd9a6574fca4c242cba299b05e0d..e595e4cb2da45de7de9e898367a0e65ffe24bc88 100644
--- a/misc/update.js
+++ b/misc/update.js
@@ -12,7 +12,11 @@ if (isJsEnabled()) {
         }
       }
 
-      var progress = new progressBar('updateprogress', updateCallback, HTTPPost);
+      errorCallback = function (pb) {
+        window.location = window.location.href.split('op=')[0] +'op=error';
+      }
+
+      var progress = new progressBar('updateprogress', updateCallback, HTTPPost, errorCallback);
       progress.setProgress(-1, 'Starting updates');
       $('progress').appendChild(progress.element);
       progress.startMonitoring('update.php?op=do_update', 0);
diff --git a/update.php b/update.php
index 7275b63e43e136a07a48e8d41016b3a192e205a2..20c3e20214b91b9b57461c2709c37ae2e8c95659 100644
--- a/update.php
+++ b/update.php
@@ -420,6 +420,9 @@ function update_do_updates() {
   return array($percentage, isset($update['module']) ? 'Updating '. $update['module'] .' module' : 'Updating complete');
 }
 
+/**
+ * Perform updates for the JS version and return progress.
+ */
 function update_do_update_page() {
   global $conf;
 
@@ -430,24 +433,27 @@ function update_do_update_page() {
     return '';
   }
 
-  // Any errors which happen would cause the result to not be parsed properly,
-  // so we need to supporess them. All errors are still logged.
-  if (!isset($conf['error_level'])) {
-    $conf['error_level'] = 0;
-  }
-  ini_set('display_errors', FALSE);
-
   list($percentage, $message) = update_do_updates();
   print drupal_to_js(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message));
 }
 
+/**
+ * Perform updates for the non-JS version and return the status page.
+ */
 function update_progress_page_nojs() {
   $new_op = 'do_update_nojs';
   if ($_SERVER['REQUEST_METHOD'] == 'GET') {
+    // Store a fallback redirect in case of a fatal PHP error
+    ob_start();
+    print '<html><head><meta http-equiv="Refresh" content="0; URL=update.php?op=error"></head></html>';
+
     list($percentage, $message) = update_do_updates();
     if ($percentage == 100) {
       $new_op = 'finished';
     }
+
+    // Updates succesful, remove fallback
+    ob_end_clean();
   }
   else {
     // This is the first page so return some output immediately.
@@ -463,15 +469,23 @@ function update_progress_page_nojs() {
   return $output;
 }
 
-function update_finished_page() {
+function update_finished_page($success) {
   drupal_set_title('Drupal database update');
   // NOTE: we can't use l() here because the URL would point to 'update.php?q=admin'.
   $links[] = '<a href="'. base_path() .'">main page</a>';
   $links[] = '<a href="'. base_path() .'?q=admin">administration pages</a>';
-  $output = '<p>Updates were attempted. If you see no failures below, you may proceed happily to the <a href="index.php?q=admin">administration pages</a>. Otherwise, you may need to update your database manually. All errors have been <a href="index.php?q=admin/logs">logged</a>.</p>';
+
+  if ($success) {
+    $output = '<p>Updates were attempted. If you see no failures below, you may proceed happily to the <a href="index.php?q=admin">administration pages</a>. Otherwise, you may need to update your database manually. All errors have been <a href="index.php?q=admin/logs">logged</a>.</p>';    
+  }
+  else {
+    $output = '<p class="error">The update process did not complete. All errors have been <a href="index.php?q=admin/logs">logged</a>. You may need to check the <code>watchdog</code> table manually.';
+  }
+
   if ($GLOBALS['access_check'] == FALSE) {
     $output .= "<p><strong>Reminder: don't forget to set the <code>\$access_check</code> value at the top of <code>update.php</code> back to <code>TRUE</code>.</strong>";
   }
+
   $output .= theme('item_list', $links);
 
   // Output a list of queries executed
@@ -546,8 +560,89 @@ function update_fix_system_table() {
   }
 }
 
+// This code may be removed later.  It is part of the Drupal 4.6 to 4.7 migration.
+function update_fix_access_table() {
+  if (variable_get('update_access_fixed', FALSE)) {
+    return;
+  }
+
+  switch ($GLOBALS['db_type']) {
+    // Only for MySQL 4.1+
+    case 'mysqli':
+      break;
+    case 'mysql':
+      if (version_compare(mysql_get_server_info($GLOBALS['active_db']), '4.1.0', '<')) {
+        return;
+      }
+      break;
+    case 'pgsql':
+      return;
+  }
+
+  // Convert access table to UTF-8 if needed.
+  $result = db_fetch_array(db_query('SHOW CREATE TABLE `access`'));
+  if (!preg_match('/utf8/i', array_pop($result))) {
+    update_convert_table_utf8('access');    
+  }
+
+  // Don't run again
+  variable_set('update_access_fixed', TRUE);
+}
+
+/**
+ * Convert a single MySQL table to UTF-8.
+ *
+ * We change all text columns to their correspending binary type,
+ * then back to text, but with a UTF-8 character set.
+ * See: http://dev.mysql.com/doc/refman/4.1/en/charset-conversion.html
+ */
+function update_convert_table_utf8($table) {
+  $ret = array();
+  $types = array('char' => 'binary',
+                 'varchar' => 'varbinary',
+                 'tinytext' => 'tinyblob',
+                 'text' => 'blob',
+                 'mediumtext' => 'mediumblob',
+                 'longtext' => 'longblob');
+
+  // Get next table in list
+  $convert_to_binary = array();
+  $convert_to_utf8 = array();
+
+  // Set table default charset
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} DEFAULT CHARACTER SET utf8');
+
+  // Find out which columns need converting and build SQL statements
+  $result = db_query('SHOW FULL COLUMNS FROM {'. $table .'}');
+  while ($column = db_fetch_array($result)) {
+    list($type) = explode('(', $column['Type']);
+    if (isset($types[$type])) {
+      $names = 'CHANGE `'. $column['Field'] .'` `'. $column['Field'] .'` ';
+      $attributes = ' DEFAULT '. ($column['Default'] == 'NULL' ? 'NULL ' :
+                     "'". db_escape_string($column['Default']) ."' ") .
+                    ($column['Null'] == 'YES' ? 'NULL' : 'NOT NULL');
+
+      $convert_to_binary[] = $names . preg_replace('/'. $type .'/i', $types[$type], $column['Type']) . $attributes;
+      $convert_to_utf8[] = $names . $column['Type'] .' CHARACTER SET utf8'. $attributes;
+    }
+  }
+
+  if (count($convert_to_binary)) {
+    // Convert text columns to binary
+    $ret[] = update_sql('ALTER TABLE {'. $table .'} '. implode(', ', $convert_to_binary));
+    // Convert binary columns to UTF-8
+    $ret[] = update_sql('ALTER TABLE {'. $table .'} '. implode(', ', $convert_to_utf8));
+  }
+  return $ret;  
+}
+
+// Some unavoidable errors happen because the database is not yet up to date.
+// We suppress them to avoid confusion. All errors are still logged.
+ini_set('display_errors', FALSE);
+
 include_once './includes/bootstrap.inc';
 update_fix_system_table();
+update_fix_access_table();
 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
 drupal_maintenance_theme();
 
@@ -567,7 +662,11 @@ function update_fix_system_table() {
       break;
 
     case 'finished':
-      $output = update_finished_page();
+      $output = update_finished_page(true);
+      break;
+
+    case 'error':
+      $output = update_finished_page(false);
       break;
 
     case 'do_update':