diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index b2f2b04..bf59d6b 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -828,14 +828,25 @@ function drupal_settings_initialize() {
  * @param $filename
  *   The filename of the item if it is to be set explicitly rather
  *   than by consulting the database.
+ * @param bool $trigger_error
+ *   Whether to trigger an error when a file is missing / moved. Defaults to
+ *   TRUE, but we may want to set this to FALSE if we merely want to check
+ *   whether an item is installed.
  *
  * @return
  *   The filename of the requested item or NULL if the item is not found.
  */
-function drupal_get_filename($type, $name, $filename = NULL) {
+function drupal_get_filename($type, $name, $filename = NULL, $trigger_error = TRUE) {
   // The location of files will not change during the request, so do not use
   // drupal_static().
-  static $files = array(), $dirs = array();
+  // The $files static variable will hold the locations of all requested files.
+  // We can be sure that any file listed in this static variable actually
+  // exists as all additions have gone through a file_exists() check.
+  static $files = array();
+
+  // This flag may hold a string that indicates where the filename was retrieved
+  // from.
+  $filename_retrieved_from = '';
 
   // Profiles are a special case: they have a fixed location and naming.
   if ($type == 'profile') {
@@ -847,68 +858,288 @@ function drupal_get_filename($type, $name, $filename = NULL) {
   }
 
   if (!empty($filename) && file_exists($filename)) {
+    // Prime the static cache with the provided filename.
     $files[$type][$name] = $filename;
+    $filename_retrieved_from = 'priming';
   }
   elseif (isset($files[$type][$name])) {
-    // nothing
+    // This item had already been found earlier in the request, either through
+    // priming of the static cache (i.e. in system_list()), through a lookup
+    // in the {system} table, or through a file scan (cached or not).
+    // Do nothing.
   }
-  // Verify that we have an active database connection, before querying
-  // the database. This is required because this function is called both
-  // before we have a database connection (i.e. during installation) and
-  // when a database connection fails.
   else {
+    // Look for the filename listed in the {system} table.
+    // Verify that we have an active database connection, before querying
+    // the database. This is required because this function is called both
+    // before we have a database connection (i.e. during installation) and
+    // when a database connection fails.
     try {
+      $system_query_success = FALSE;
       if (function_exists('db_query')) {
-        $file = db_query("SELECT filename FROM {system} WHERE name = :name AND type = :type", array(':name' => $name, ':type' => $type))->fetchField();
-        if ($file !== FALSE && file_exists(DRUPAL_ROOT . '/' . $file)) {
-          $files[$type][$name] = $file;
+        $file = db_query("SELECT filename FROM {system} WHERE name = :name AND type = :type", array(
+          ':name' => $name,
+          ':type' => $type
+        ))->fetchField();
+        if ($file !== FALSE) {
+          if (file_exists(DRUPAL_ROOT . '/' . $file)) {
+            $files[$type][$name] = $file;
+            $filename_retrieved_from = 'system_table';
+          }
         }
+        $system_query_success = TRUE;
       }
     }
     catch (Exception $e) {
       // The database table may not exist because Drupal is not yet installed,
-      // or the database might be down. We have a fallback for this case so we
-      // hide the error completely.
-    }
-    // Fallback to searching the filesystem if the database could not find the
-    // file or the file returned by the database is not found.
+      // the database might be down, or we may have done a non-database cache
+      // flush while $conf['page_cache_without_database'] = TRUE and
+      // $conf['page_cache_invoke_hooks'] = TRUE. We have a fallback for these
+      // cases so we hide the error completely.
+      // We also hide the trigger_error() "moved or newly introduced" warning
+      // in this case as we can't be sure if it's correct.
+    }
+    if (!$system_query_success) {
+      // We can't tell if the error message will be valid if the query to the
+      // {system} table failed, so skip triggering of errors.
+      $trigger_error = FALSE;
+    }
+    // Fall back to searching the filesystem if the database could not find the
+    // file or the file does not exist at the path returned by the database.
     if (!isset($files[$type][$name])) {
-      // We have a consistent directory naming: modules, themes...
-      $dir = $type . 's';
-      if ($type == 'theme_engine') {
-        $dir = 'themes/engines';
-        $extension = 'engine';
-      }
-      elseif ($type == 'theme') {
-        $extension = 'info';
-      }
-      else {
-        $extension = $type;
-      }
+      $files[$type][$name] = _drupal_get_filename_fallback($type, $name, $trigger_error);
+    }
+  }
 
-      if (!isset($dirs[$dir][$extension])) {
-        $dirs[$dir][$extension] = TRUE;
-        if (!function_exists('drupal_system_listing')) {
-          require_once DRUPAL_ROOT . '/includes/common.inc';
-        }
-        // Scan the appropriate directories for all files with the requested
-        // extension, not just the file we are currently looking for. This
-        // prevents unnecessary scans from being repeated when this function is
-        // called more than once in the same page request.
-        $matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir, 'name', 0);
-        foreach ($matches as $matched_name => $file) {
-          $files[$type][$matched_name] = $file->uri;
+  if (isset($files[$type][$name])) {
+    // We found a file.
+    // Get the static file scan list. This list may be incomplete as there
+    // may be further files listed in persistent cache, but we skip loading that
+    // as a performance optimization, as the persistent cache record will
+    // be cleared on every system_rebuild_module_data() anyway.
+    $file_scans = &drupal_static('_drupal_get_filename_fallback');
+    // If this file had previously been marked as missing, clean up the entry
+    // from the file scan cache.
+    $missing_reappeared = isset($file_scans[$type][$name]) && $file_scans[$type][$name] === FALSE;
+    // If this file had previously been marked as moved, and reappeared
+    // at the location listed in the system table or in the primed static,
+    // clean up the entry from the file scan cache.
+    $moved_reappeared = isset($file_scans[$type][$name]) && is_string($file_scans[$type][$name]) && ($filename_retrieved_from == 'system_table' || $filename_retrieved_from == 'priming');
+    if ($missing_reappeared || $moved_reappeared) {
+      $file_scans[$type][$name] = NULL;
+      $file_scans['#write_cache'] = TRUE;
+    }
+    return $files[$type][$name];
+  }
+}
+
+/**
+ * Helper function for drupal_get_filename().
+ *
+ * This function looks for the requested file by triggering a file scan,
+ * caching the new location if the file has moved and caching the miss
+ * if the file is missing. If a file had been marked as missing or moved
+ * in a previous file scan, no new file scan will be performed.
+ *
+ * @param string $type
+ *   The type of the item (theme, theme_engine, module, profile).
+ * @param string $name
+ *   The name of the item for which the filename is requested.
+ * @param bool $trigger_error
+ *   Whether to create an error when a file is missing or moved.
+ *
+ * @return string
+ *   The filename of the requested item or NULL if the item is not found.
+ */
+function _drupal_get_filename_fallback($type, $name, $trigger_error) {
+  // This static variable will hold all missing and moved files, in order
+  // to prevent unnecessary file scans. It is an associative array with as
+  // keys the type and name of the item, and as value:
+  // - the boolean FALSE if the item is missing
+  // - a string with location found in a file scan if the module/theme has moved
+  //   from the location listed in the {system} table.
+  $file_scans = &drupal_static('_drupal_get_filename_fallback');
+  if (!isset($file_scans)) {
+    $file_scans = array();
+  }
+  // Get the list of files marked as missing and moved during earlier file scans
+  // from the persistent cache, if available.
+  if (!isset($file_scans['#cache_merge_done'])) {
+    try {
+      if (function_exists('cache_get')) {
+        $cache = cache_get('_drupal_get_filename_fallback', 'cache_bootstrap');
+        if (!empty($cache->data)) {
+          // Merge the changes to the file scan cache done in the current
+          // request (including the setting of previously set records to NULL)
+          // with the values that had been stored in the persistent cache.
+          $file_scans = drupal_array_merge_deep($cache->data, $file_scans);
+          // Set a flag so we remember that we've done a merge with the values
+          // stored in the persistent cache.
         }
+        $file_scans['#cache_merge_done'] = TRUE;
+      }
+    }
+    catch (Exception $e) {
+      // Hide the error.
+    }
+  }
+
+  // Check whether this file had moved from the path listed in the {system}
+  // table during a previous file scan.
+  if (isset($file_scans[$type][$name]) && is_string($file_scans[$type][$name]) && file_exists($file_scans[$type][$name])) {
+    // The file exists at the location cached in a previous file scan.
+    $filename_from_file_scan = $file_scans[$type][$name];
+  }
+  // Unless the module had been marked as missing, perform a file scan.
+  elseif (!(isset($file_scans[$type][$name]) && $file_scans[$type][$name] === FALSE)) {
+    $filename_from_file_scan = _drupal_get_filename_perform_file_scan($type, $name);
+  }
+
+  if (isset($filename_from_file_scan)) {
+    // The file has either moved from the location in the {system} table
+    // or is not yet listed in the {system} table.
+    // Make sure our change to the file scan cache will be written to the
+    // persistent cache.
+    if (!isset($file_scans[$type][$name]) || isset($file_scans[$type][$name]) && $file_scans[$type][$name] != $filename_from_file_scan) {
+      $file_scans[$type][$name] = $filename_from_file_scan;
+      if (!$trigger_error) {
+        // If we skip error triggering, do not write the found file location to
+        // persistent cache.
+        $file_scans['#write_cache'] = TRUE;
       }
     }
+    // Create a user-level warning about the moved / recently introduced file.
+    if ($trigger_error) {
+      _drupal_get_filename_fallback_trigger_error($type, $name, 'moved');
+    }
+    return $filename_from_file_scan;
+  }
+  else {
+    // Make sure our change to the file scan cache will be written to
+    // the persistent cache.
+    if (!(isset($file_scans[$type][$name]) && $file_scans[$type][$name] === FALSE)) {
+      $file_scans['#write_cache'] = TRUE;
+    }
+    // Mark the file as missing.
+    $file_scans[$type][$name] = FALSE;
+    // Create a user-level warning about the missing file.
+    if ($trigger_error) {
+      _drupal_get_filename_fallback_trigger_error($type, $name, 'missing');
+    }
+  }
+}
+
+/**
+ * Helper function for _drupal_get_filename_fallback().
+ *
+ * Creates a user-level warning when a missing or moved file is detected in
+ * _drupal_get_filename_fallback().
+ *
+ * @param $type
+ *   The type of the item (theme, theme_engine, module, profile).
+ * @param $name
+ *   The name of the item for which the filename is requested.
+ * @param $error_type
+ *   The type of the error ('missing' or 'moved').
+ */
+function _drupal_get_filename_fallback_trigger_error($type, $name, $error_type) {
+  // Make sure we only show any missing / moved file error only once per
+  // request.
+  static $errors_triggered = array();
+  if (empty($errors_triggered[$type][$name][$error_type])) {
+    if ($error_type == 'missing') {
+      trigger_error(format_string('The following @type is missing from the file system: %name. In order to fix this, put the file back in its original location or uninstall the module. For more information, see <a href="@documentation">the documentation page</a>.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING);
+    }
+    elseif ($error_type == 'moved') {
+      trigger_error(format_string('The following @type has moved or has recently appeared within the file system: %name. In order to fix this, clear caches or put the file back in its original location. For more information, see <a href="@documentation">the documentation page</a>.', array('@type' => $type, '%name' => $name, '@documentation' => 'https://www.drupal.org/node/2487215')), E_USER_WARNING);
+    }
+    $errors_triggered[$type][$name][$error_type] = TRUE;
+  }
+}
+
+/**
+ * Helper function for drupal_get_filename().
+ *
+ * Performs a file scan.
+ *
+ * @param $type
+ *   The type of the item (theme, theme_engine, module, profile).
+ * @param $name
+ *   The name of the item for which the filename is requested.
+ *
+ * @return
+ *   The filename of the requested item or NULL if the item is not found.
+ */
+function _drupal_get_filename_perform_file_scan($type, $name) {
+  static $dirs = array(), $files = array();
+
+  // We have a consistent directory naming: modules, themes...
+  $dir = $type . 's';
+  if ($type == 'theme_engine') {
+    $dir = 'themes/engines';
+    $extension = 'engine';
+  }
+  elseif ($type == 'theme') {
+    $extension = 'info';
+  }
+  else {
+    $extension = $type;
+  }
+
+  // Check if we had already scanned this directory/extension combination.
+  if (!isset($dirs[$dir][$extension])) {
+    // Log that we have now scanned this directory/extension combination
+    // into a static variable so as to prevent unnecessary file scans.
+    $dirs[$dir][$extension] = TRUE;
+    if (!function_exists('drupal_system_listing')) {
+      require_once DRUPAL_ROOT . '/includes/common.inc';
+    }
+    // Scan the appropriate directories for all files with the requested
+    // extension, not just the file we are currently looking for. This
+    // prevents unnecessary scans from being repeated when this function is
+    // called more than once in the same page request.
+    $matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir, 'name', 0);
+    foreach ($matches as $matched_name => $file) {
+      // Log the locations found in the file scan into a static variable.
+      $files[$type][$matched_name] = $file->uri;
+    }
   }
 
+  // If current file had been found in a file scan earlier on in this
+  // request, use the new location that had been found in the file scan.
   if (isset($files[$type][$name])) {
     return $files[$type][$name];
   }
 }
 
 /**
+ * Writes the file scan cache to the persistent cache.
+ *
+ * This cache stores all files marked as missing or moved after a file scan
+ * to prevent unnecessary file scans in subsequent requests. This cache is
+ * cleared in system_list_reset() (i.e. after a module/theme rebuild).
+ */
+function drupal_file_scan_write_cache() {
+  // Only write to the persistent cache if we are fully bootstrapped.
+  if (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL) {
+    return;
+  }
+  $file_scans = &drupal_static('_drupal_get_filename_fallback');
+  if (isset($file_scans['#write_cache'])) {
+    // Merge the newly found out missing and moved file data with the previously
+    // existing data, if we had not done so yet.
+    if (!isset($file_scans['#cache_merge_done'])) {
+      $cache = cache_get('_drupal_get_filename_fallback', 'cache_bootstrap');
+      if (isset($cache->data)) {
+        $file_scans = drupal_array_merge_deep($cache->data, $file_scans);
+      }
+    }
+    $file_scans['#write_cache'] = NULL;
+    cache_set('_drupal_get_filename_fallback', $file_scans, 'cache_bootstrap');
+  }
+}
+
+/**
  * Loads the persistent variable table.
  *
  * The variable table is composed of values that have been saved in the table
diff --git a/includes/common.inc b/includes/common.inc
index ceac115..414dab0 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -2751,6 +2751,7 @@ function drupal_page_footer() {
   _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
   drupal_cache_system_paths();
   module_implements_write_cache();
+  drupal_file_scan_write_cache();
   system_run_automated_cron();
 }
 
diff --git a/includes/module.inc b/includes/module.inc
index 076992c..101f5ca 100644
--- a/includes/module.inc
+++ b/includes/module.inc
@@ -208,9 +208,10 @@ function system_list($type) {
       }
       cache_set('system_list', $lists, 'cache_bootstrap');
     }
-    // To avoid a separate database lookup for the filepath, prime the
-    // drupal_get_filename() static cache with all enabled modules and themes.
     foreach ($lists['filepaths'] as $item) {
+      // To avoid a separate database lookup for the file path, prime the
+      // drupal_get_filename() static cache with all enabled modules and all
+      // themes.
       drupal_get_filename($item['type'], $item['name'], $item['filepath']);
     }
   }
@@ -227,6 +228,11 @@ function system_list_reset() {
   drupal_static_reset('list_themes');
   cache_clear_all('bootstrap_modules', 'cache_bootstrap');
   cache_clear_all('system_list', 'cache_bootstrap');
+
+  // Clean up the bootstrap file scan cache.
+  drupal_static_reset('_drupal_get_filename_fallback');
+  drupal_static_reset('system_filepaths');
+  cache_clear_all('_drupal_get_filename_fallback', 'cache_bootstrap');
 }
 
 /**
diff --git a/includes/update.inc b/includes/update.inc
index a17161c..4f12db8 100644
--- a/includes/update.inc
+++ b/includes/update.inc
@@ -795,6 +795,14 @@ function update_fix_d7_requirements() {
 function update_fix_d7_install_profile() {
   $profile = drupal_get_profile();
 
+  // 'Default' profile has been renamed to 'Standard' in D7.
+  // We change the profile here to prevent a broken record in the system table.
+  // See system_update_7049()
+  if ($profile == 'default') {
+    $profile = 'standard';
+    variable_set('install_profile', $profile);
+  }
+
   $results = db_select('system', 's')
     ->fields('s', array('name', 'schema_version'))
     ->condition('name', $profile)
diff --git a/modules/simpletest/simpletest.module b/modules/simpletest/simpletest.module
index 91f0f90..a2e6c6e 100644
--- a/modules/simpletest/simpletest.module
+++ b/modules/simpletest/simpletest.module
@@ -371,7 +371,11 @@ function simpletest_test_get_all() {
           // If this test class requires a non-existing module, skip it.
           if (!empty($info['dependencies'])) {
             foreach ($info['dependencies'] as $module) {
-              if (!drupal_get_filename('module', $module)) {
+              // Pass FALSE as fourth argument so no error gets created for
+              // the missing file.
+              $found_module = drupal_get_filename('module', $module, NULL, FALSE);
+
+              if (!$found_module) {
                 continue 2;
               }
             }
diff --git a/modules/simpletest/tests/bootstrap.test b/modules/simpletest/tests/bootstrap.test
index ece1cd9..9088023 100644
--- a/modules/simpletest/tests/bootstrap.test
+++ b/modules/simpletest/tests/bootstrap.test
@@ -371,7 +371,7 @@ class HookBootExitTestCase extends DrupalWebTestCase {
 /**
  * Test drupal_get_filename()'s availability.
  */
-class BootstrapGetFilenameTestCase extends DrupalUnitTestCase {
+class BootstrapGetFilenameTestCase extends DrupalWebTestCase {
 
   public static function getInfo() {
     return array(
@@ -382,6 +382,15 @@ class BootstrapGetFilenameTestCase extends DrupalUnitTestCase {
   }
 
   /**
+   * Whether the filename test triggered the right error.
+   *
+   * Used by BootstrapGetFilenameTestCase::testDrupalGetFilename().
+   *
+   * @var boolean
+   */
+  protected $getFilenameTestTriggeredError = FALSE;
+
+  /**
    * Test that drupal_get_filename() works correctly when the file is not found in the database.
    */
   function testDrupalGetFilename() {
@@ -410,6 +419,40 @@ class BootstrapGetFilenameTestCase extends DrupalUnitTestCase {
     // automatically check there for 'script' files, just as it does for (e.g.)
     // 'module' files in modules.
     $this->assertIdentical(drupal_get_filename('script', 'test'), 'scripts/test.script', t('Retrieve test script location.'));
+
+    // Generate a non-existing module name.
+    $non_existing_module = $this->randomName();
+
+    // Searching for an item that does not exist returns NULL.
+    // Set a custom error handler so we can ignore the file not found error.
+    set_error_handler(array($this, 'fileNotFoundErrorHandler'));
+    $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for an item that does not exist returns NULL.');
+    $this->assertTrue($this->getFilenameTestTriggeredError, 'Searching for an item that does not exist triggers an error.');
+    // Restore the original error handler.
+    restore_error_handler();
+
+    // Get the missing and moved files static variable.
+    $file_scans = &drupal_static('_drupal_get_filename_fallback');
+
+    // Searching for an item that does not exist creates a record in the static
+    // variable.
+    $this->assertTrue($file_scans['module'][$non_existing_module] === FALSE, 'Searching for an item that does not exist creates a record in the missing and moved files static variable.');
+    drupal_install_schema('system');
+    drupal_file_scan_write_cache();
+    $cache = cache_get('_drupal_get_filename_fallback', 'cache_bootstrap');
+    $this->assertTrue($cache->data['module'][$non_existing_module] === FALSE, 'File scan results are correctly saved in persistent cache.');
+  }
+
+  /**
+   * Skips handling of "file not found" errors.
+   */
+  public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) {
+    // Skip error handling if this is a "file not found" error.
+    if (strstr($message, 'is missing from the file system:')) {
+      $this->getFilenameTestTriggeredError = TRUE;
+      return;
+    }
+    _drupal_error_handler($error_level, $message, $filename, $line, $context);
   }
 }
 
diff --git a/modules/system/system.updater.inc b/modules/system/system.updater.inc
index a14d788..2a32c4b 100644
--- a/modules/system/system.updater.inc
+++ b/modules/system/system.updater.inc
@@ -24,7 +24,7 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface {
    * found on your system, and if there was a copy in sites/all, we'd see it.
    */
   public function getInstallDirectory() {
-    if ($relative_path = drupal_get_path('module', $this->name)) {
+    if ($this->isInstalled() && ($relative_path = drupal_get_path('module', $this->name))) {
       $relative_path = dirname($relative_path);
     }
     else {
@@ -34,7 +34,7 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface {
   }
 
   public function isInstalled() {
-    return (bool) drupal_get_path('module', $this->name);
+    return (bool) drupal_get_filename('module', $this->name, NULL, FALSE);
   }
 
   public static function canUpdateDirectory($directory) {
@@ -109,7 +109,7 @@ class ThemeUpdater extends Updater implements DrupalUpdaterInterface {
    * found on your system, and if there was a copy in sites/all, we'd see it.
    */
   public function getInstallDirectory() {
-    if ($relative_path = drupal_get_path('theme', $this->name)) {
+    if ($this->isInstalled() && ($relative_path = drupal_get_path('theme', $this->name))) {
       $relative_path = dirname($relative_path);
     }
     else {
@@ -119,7 +119,7 @@ class ThemeUpdater extends Updater implements DrupalUpdaterInterface {
   }
 
   public function isInstalled() {
-    return (bool) drupal_get_path('theme', $this->name);
+    return (bool) drupal_get_filename('theme', $this->name, NULL, FALSE);
   }
 
   static function canUpdateDirectory($directory) {
