Index: src/wp-admin/includes/class-wp-upgrader.php
===================================================================
--- src/wp-admin/includes/class-wp-upgrader.php	(revision 32814)
+++ src/wp-admin/includes/class-wp-upgrader.php	(working copy)
@@ -120,30 +120,31 @@ class WP_Upgrader {
 		$this->strings['fs_unavailable'] = __('Could not access filesystem.');
 		$this->strings['fs_error'] = __('Filesystem error.');
 		$this->strings['fs_no_root_dir'] = __('Unable to locate WordPress Root directory.');
 		$this->strings['fs_no_content_dir'] = __('Unable to locate WordPress Content directory (wp-content).');
 		$this->strings['fs_no_plugins_dir'] = __('Unable to locate WordPress Plugin directory.');
 		$this->strings['fs_no_themes_dir'] = __('Unable to locate WordPress Theme directory.');
 		/* translators: %s: directory name */
 		$this->strings['fs_no_folder'] = __('Unable to locate needed folder (%s).');
 
 		$this->strings['download_failed'] = __('Download failed.');
 		$this->strings['installing_package'] = __('Installing the latest version&#8230;');
 		$this->strings['no_files'] = __('The package contains no files.');
 		$this->strings['folder_exists'] = __('Destination folder already exists.');
 		$this->strings['mkdir_failed'] = __('Could not create directory.');
 		$this->strings['incompatible_archive'] = __('The package could not be installed.');
+		$this->strings['files_not_writable'] = __( 'The update cannot be installed because we will be unable to copy some files. This is usually due to inconsistent file permissions.' );
 
 		$this->strings['maintenance_start'] = __('Enabling Maintenance mode&#8230;');
 		$this->strings['maintenance_end'] = __('Disabling Maintenance mode&#8230;');
 	}
 
 	/**
 	 * Connect to the filesystem.
 	 *
 	 * @since 2.8.0
 	 *
 	 * @global WP_Filesystem_Base $wp_filesystem Subclass
 	 *
 	 * @param array $directories                  Optional. A list of directories. If any of these do
 	 *                                            not exist, a {@see WP_Error} object will be returned.
 	 *                                            Default empty array.
@@ -281,30 +282,88 @@ class WP_Upgrader {
 		if ( $delete_package )
 			unlink($package);
 
 		if ( is_wp_error($result) ) {
 			$wp_filesystem->delete($working_dir, true);
 			if ( 'incompatible_archive' == $result->get_error_code() ) {
 				return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $result->get_error_data() );
 			}
 			return $result;
 		}
 
 		return $working_dir;
 	}
 
 	/**
+	 * Clears the directory where this item is going to be installed into.
+	 *
+	 * @since 4.3.0
+	 *
+	 * @global WP_Filesystem_Base $wp_filesystem Subclass
+	 *
+	 * @param string $remote_destination The location on the remote filesystem to be cleared
+	 *
+	 * @return bool|WP_Error true upon success, {@see WP_Error} on failure.
+	 */
+	function clear_destination( $remote_destination ) {
+		global $wp_filesystem;
+
+		if ( ! $wp_filesystem->exists( $remote_destination ) ) {
+			return true;
+		}
+
+		// Check all files are writable before attempting to clear the destination
+		$unwritable_files = array();
+
+		$_files = $wp_filesystem->dirlist( $remote_destination, true, true );
+		// Flatten the resulting array, iterate using each as we append to the array during iteration
+		while ( $f = each( $_files ) ) {
+			$file = $f['value'];
+			$name = $f['key'];
+
+			if ( ! isset( $file['files'] ) ) {
+				continue;
+			}
+
+			foreach ( $file['files'] as $filename => $details ) {
+				$_files[ $name . '/' . $filename ] = $details;
+			}
+		}
+
+		// Check writability
+		foreach ( $_files as $filename => $file_details ) {
+			if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
+				// Attempt to alter permissions to allow writes and try again
+				$wp_filesystem->chmod( $remote_destination . $filename, ( 'd' == $file_details['type'] ? FS_CHMOD_DIR : FS_CHMOD_FILE ) );
+				if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
+					$unwritable_files[] = $filename;
+				}
+			}
+		}
+
+		if ( ! empty( $unwritable_files ) ) {
+			return new WP_Error( 'files_not_writable', $this->strings['files_not_writable'], implode( ', ', $unwritable_files ) );
+		}
+
+		if ( ! $wp_filesystem->delete( $remote_destination, true ) ) {
+			return new WP_Error( 'remove_old_failed', $this->strings['remove_old_failed'] );
+		}
+
+		return true;
+	}
+
+	/**
 	 * Install a package.
 	 *
 	 * Copies the contents of a package form a source directory, and installs them in
 	 * a destination directory. Optionally removes the source. It can also optionally
 	 * clear out the destination folder if it already exists.
 	 *
 	 * @since 2.8.0
 	 *
 	 * @global WP_Filesystem_Base $wp_filesystem Subclass
 	 * @global array              $wp_theme_directories
 	 *
 	 * @param array|string $args {
 	 *     Optional. Array or string of arguments for installing a package. Default empty array.
 	 *
 	 *     @type string $source                      Required path to the package source. Default empty.
@@ -405,53 +464,49 @@ class WP_Upgrader {
 		 * destination directory (WP_PLUGIN_DIR / wp-content/themes) intending
 		 * to copy the directory into the directory, whilst they pass the source
 		 * as the actual files to copy.
 		 */
 		$protected_directories = array( ABSPATH, WP_CONTENT_DIR, WP_PLUGIN_DIR, WP_CONTENT_DIR . '/themes' );
 
 		if ( is_array( $wp_theme_directories ) ) {
 			$protected_directories = array_merge( $protected_directories, $wp_theme_directories );
 		}
 		if ( in_array( $destination, $protected_directories ) ) {
 			$remote_destination = trailingslashit( $remote_destination ) . trailingslashit( basename( $source ) );
 			$destination = trailingslashit( $destination ) . trailingslashit( basename( $source ) );
 		}
 
 		if ( $clear_destination ) {
-			//We're going to clear the destination if there's something there
+			// We're going to clear the destination if there's something there
 			$this->skin->feedback('remove_old');
-			$removed = true;
-			if ( $wp_filesystem->exists( $remote_destination ) ) {
-				$removed = $wp_filesystem->delete( $remote_destination, true );
-			}
+
+			$removed = $this->clear_destination( $remote_destination );
 
 			/**
 			 * Filter whether the upgrader cleared the destination.
 			 *
 			 * @since 2.8.0
 			 *
-			 * @param bool   $removed            Whether the destination was cleared.
+			 * @param mixed  $removed            Whether the destination was cleared. true on success, WP_Error on failure
 			 * @param string $local_destination  The local package destination.
 			 * @param string $remote_destination The remote package destination.
 			 * @param array  $hook_extra         Extra arguments passed to hooked filters.
 			 */
 			$removed = apply_filters( 'upgrader_clear_destination', $removed, $local_destination, $remote_destination, $args['hook_extra'] );
 
-			if ( is_wp_error($removed) ) {
+			if ( is_wp_error( $removed ) ) {
 				return $removed;
-			} elseif ( ! $removed ) {
-				return new WP_Error('remove_old_failed', $this->strings['remove_old_failed']);
 			}
 		} elseif ( $args['abort_if_destination_exists'] && $wp_filesystem->exists($remote_destination) ) {
 			//If we're not clearing the destination folder and something exists there already, Bail.
 			//But first check to see if there are actually any files in the folder.
 			$_files = $wp_filesystem->dirlist($remote_destination);
 			if ( ! empty($_files) ) {
 				$wp_filesystem->delete($remote_source, true); //Clear out the source files.
 				return new WP_Error('folder_exists', $this->strings['folder_exists'], $remote_destination );
 			}
 		}
 
 		//Create destination if needed
 		if ( ! $wp_filesystem->exists( $remote_destination ) ) {
 			if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) ) {
 				return new WP_Error( 'mkdir_failed_destination', $this->strings['mkdir_failed'], $remote_destination );
