diff --git a/core/includes/file.inc b/core/includes/file.inc
index b476bc7..55c3b1f 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -125,6 +125,26 @@ const FILE_EXISTS_REPLACE = 1;
 const FILE_EXISTS_ERROR = 2;
 
 /**
+ * Flag for determining if a file exists: Return TRUE if the file itself exists.
+ */
+const FILE_CHECK_EXISTS_FILE = 0;
+
+/**
+ * Flag for determining if a file exists: Return TRUE if the file entity exists.
+ */
+const FILE_CHECK_EXISTS_ENTITY = 1;
+
+/**
+ * Flag for determining if a file exists: Return TRUE if both the file and the file entity exist.
+ */
+const FILE_CHECK_EXISTS_FILE_AND_ENTITY = 2;
+
+/**
+ * Flag for determining if a file exists: Return TRUE if either the file or the file entity exists.
+ */
+const FILE_CHECK_EXISTS_FILE_OR_ENTITY = 3;
+
+/**
  * Indicates that the file is permanent and should not be deleted.
  *
  * Temporary files older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed
@@ -617,6 +637,23 @@ function file_load($fid) {
 }
 
 /**
+ * Load a single file object by URI.
+ *
+ * @param $uri
+ *   A file URI.
+ *
+ * @return
+ *   An object representing the file, or FALSE if the file was not found.
+ *
+ * @see hook_file_load()
+ * @see file_load_multiple()
+ */
+function file_load_by_uri($uri) {
+  $files = file_load_multiple(array(), array('uri' => $uri));
+  return reset($files);
+}
+
+/**
  * Saves a file object to the database.
  *
  * If the $file->fid is not set a new record will be added.
@@ -1013,13 +1050,15 @@ function file_build_uri($path) {
  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
  *       unique.
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
+ * @param $check
+ *   Which file existence check to perform. Passed to drupal_file_exists().
  *
  * @return
  *   The destination filepath, or FALSE if the file already exists
  *   and FILE_EXISTS_ERROR is specified.
  */
-function file_destination($destination, $replace) {
-  if (file_exists($destination)) {
+function file_destination($destination, $replace, $check = FILE_CHECK_EXISTS_FILE) {
+  if (drupal_file_exists($destination, $check)) {
     switch ($replace) {
       case FILE_EXISTS_REPLACE:
         // Do nothing here, we want to overwrite the existing file.
@@ -1028,7 +1067,7 @@ function file_destination($destination, $replace) {
       case FILE_EXISTS_RENAME:
         $basename = drupal_basename($destination);
         $directory = drupal_dirname($destination);
-        $destination = file_create_filename($basename, $directory);
+        $destination = file_create_filename($basename, $directory, $check);
         break;
 
       case FILE_EXISTS_ERROR:
@@ -1235,12 +1274,14 @@ function file_unmunge_filename($filename) {
  *   String filename
  * @param $directory
  *   String containing the directory or parent URI.
+ * @param $check
+ *   Which file existence check to perform. Passed to drupal_file_exists().
  *
  * @return
  *   File path consisting of $directory and a unique filename based off
  *   of $basename.
  */
-function file_create_filename($basename, $directory) {
+function file_create_filename($basename, $directory, $check = FILE_CHECK_EXISTS_FILE) {
   // Strip control characters (ASCII value < 32). Though these are allowed in
   // some filesystems, not many applications handle them well.
   $basename = preg_replace('/[\x00-\x1F]/u', '_', $basename);
@@ -1259,7 +1300,7 @@ function file_create_filename($basename, $directory) {
 
   $destination = $directory . $separator . $basename;
 
-  if (file_exists($destination)) {
+  if (drupal_file_exists($destination, $check)) {
     // Destination file already exists, generate an alternative.
     $pos = strrpos($basename, '.');
     if ($pos !== FALSE) {
@@ -1274,7 +1315,7 @@ function file_create_filename($basename, $directory) {
     $counter = 0;
     do {
       $destination = $directory . $separator . $name . '_' . $counter++ . $ext;
-    } while (file_exists($destination));
+    } while (drupal_file_exists($destination, $check));
   }
 
   return $destination;
@@ -1588,7 +1629,7 @@ function file_save_upload($source, $validators = array(), $destination = FALSE,
   if (substr($destination, -1) != '/') {
     $destination .= '/';
   }
-  $file->destination = file_destination($destination . $file->filename, $replace);
+  $file->destination = file_destination($destination . $file->filename, $replace, FILE_CHECK_EXISTS_FILE_OR_ENTITY);
   // If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and
   // there's an existing file so we need to bail.
   if ($file->destination === FALSE) {
@@ -2275,6 +2316,40 @@ function drupal_unlink($uri, $context = NULL) {
 }
 
 /**
+ * Checks whether a file exists.
+ *
+ * PHP's file_exists() only checks the file itself, not the Drupal entity.
+ *
+ * @param $uri
+ *   A URI or pathname.
+ * @param $check
+ *   One of:
+ *   - FILE_CHECK_EXISTS_FILE
+ *   - FILE_CHECK_EXISTS_ENTITY
+ *   - FILE_CHECK_EXISTS_FILE_AND_ENTITY
+ *   - FILE_CHECK_EXISTS_FILE_OR_ENTITY
+ *
+ * @return
+ *   Boolean TRUE if the file exists based on the requested check. FALSE
+ *   otherwise.
+ *
+ * @see file_exists()
+ * @ingroup php_wrappers
+ */
+function drupal_file_exists($uri, $check = FILE_CHECK_EXISTS_FILE) {
+  switch ($check) {
+    case FILE_CHECK_EXISTS_FILE:
+      return file_exists($uri);
+    case FILE_CHECK_EXISTS_ENTITY:
+      return (bool) file_load_by_uri($uri);
+    case FILE_CHECK_EXISTS_FILE_AND_ENTITY:
+      return file_exists($uri) && file_load_by_uri($uri);
+    case FILE_CHECK_EXISTS_FILE_OR_ENTITY:
+      return file_exists($uri) || file_load_by_uri($uri);
+  }
+}
+
+/**
  * Returns the absolute local filesystem path of a stream URI.
  *
  * This function was originally written to ease the conversion of 6.x code to
diff --git a/core/modules/system/tests/file.test b/core/modules/system/tests/file.test
index ab93009..6483486 100644
--- a/core/modules/system/tests/file.test
+++ b/core/modules/system/tests/file.test
@@ -857,6 +857,14 @@ class FileSaveUploadTest extends FileHookTestCase {
   }
 
   /**
+   * Test renaming when uploading over a file that exists in {file_managed} but not on disk.
+   */
+  function testZombieRename() {
+    $this->killFile('temporary://' . drupal_basename($this->image->uri));
+    $this->testExistingRename();
+  }
+
+  /**
    * Test replacement when uploading over a file that already exists.
    */
   function testExistingReplace() {
@@ -873,6 +881,14 @@ class FileSaveUploadTest extends FileHookTestCase {
   }
 
   /**
+   * Test replacement when uploading over a file that exists in {file_managed} but not on disk.
+   */
+  function testZombieReplace() {
+    $this->killFile('temporary://' . drupal_basename($this->image->uri));
+    $this->testExistingReplace();
+  }
+
+  /**
    * Test for failure when uploading over a file that already exists.
    */
   function testExistingError() {
@@ -889,12 +905,27 @@ class FileSaveUploadTest extends FileHookTestCase {
   }
 
   /**
+   * Test for failure when uploading over a file that exists in {file_managed} but not on disk.
+   */
+  function testZombieError() {
+    $this->killFile('temporary://' . drupal_basename($this->image->uri));
+    $this->testExistingError();
+  }
+
+  /**
    * Test for no failures when not uploading a file.
    */
   function testNoUpload() {
     $this->drupalPost('file-test/upload', array(), t('Submit'));
     $this->assertNoRaw(t('Epic upload FAIL!'), t('Failure message not found.'));
   }
+
+  /**
+   * Deletes a file from disk without deleting its {file_managed} record.
+   */
+  private function killFile($uri) {
+    $this->assertTrue(drupal_file_exists($uri, FILE_CHECK_EXISTS_FILE_AND_ENTITY) && file_unmanaged_delete($uri));
+  }
 }
 
 /**
