diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
index 3238ce6..98067f6 100644
--- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
+++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
@@ -96,9 +96,11 @@ class ExtensionDiscovery {
    * @param string $root
    *   The app root.
    */
-  public function __construct($root) {
+  public function __construct($root, $use_file_cache = TRUE, $profile_directories = NULL, $site_path = NULL) {
     $this->root = $root;
-    $this->fileCache = FileCacheFactory::get('extension_discovery');
+    $this->fileCache = $use_file_cache ? FileCacheFactory::get('extension_discovery') : NULL;
+    $this->profileDirectories = $profile_directories;
+    $this->sitePath = $site_path;
   }
 
   /**
@@ -172,7 +174,7 @@ public function scan($type, $include_tests = NULL) {
       $searchdirs[static::ORIGIN_SITE] = \Drupal::service('site.path');
     }
     else {
-      $searchdirs[static::ORIGIN_SITE] = DrupalKernel::findSitePath(Request::createFromGlobals());
+      $searchdirs[static::ORIGIN_SITE] = isset($this->sitePath) ? $this->sitePath : DrupalKernel::findSitePath(Request::createFromGlobals());
     }
 
     // Unless an explicit value has been passed, manually check whether we are
@@ -427,7 +429,7 @@ protected function scanDirectory($dir, $include_tests) {
         continue;
       }
 
-      if ($cached_extension = $this->fileCache->get($fileinfo->getPathName())) {
+      if ($this->fileCache && $cached_extension = $this->fileCache->get($fileinfo->getPathName())) {
         $files[$cached_extension->getType()][$key] = $cached_extension;
         continue;
       }
@@ -467,7 +469,10 @@ protected function scanDirectory($dir, $include_tests) {
       $extension->origin = $dir;
 
       $files[$type][$key] = $extension;
-      $this->fileCache->set($fileinfo->getPathName(), $extension);
+
+      if ($this->fileCache) {
+        $this->fileCache->set($fileinfo->getPathName(), $extension);
+      }
     }
     return $files;
   }
diff --git a/core/lib/Drupal/Core/Update/UpdateRegistry.php b/core/lib/Drupal/Core/Update/UpdateRegistry.php
new file mode 100644
index 0000000..0a7e90d
--- /dev/null
+++ b/core/lib/Drupal/Core/Update/UpdateRegistry.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Update\UpdateRegistry.
+ */
+
+namespace Drupal\Core\Update;
+
+use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
+
+/**
+ * Provides all and missing hook_update_data_N implementations.
+ */
+class UpdateRegistry {
+
+  protected $updateName = 'update_data';
+
+  /**
+   * The app root.
+   *
+   * @var string
+   */
+  protected $root;
+
+  /**
+   * The filename of the log file.
+   *
+   * @var string
+   */
+  protected $logFilename;
+
+  /**
+   * @var string[]
+   */
+  protected $enabledModules;
+
+  /**
+   * The key value storage.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   */
+  protected $keyValue;
+
+  public function __construct($root, $site_path, array $enabled_modules, KeyValueStoreInterface $key_value) {
+    $this->root = $root;
+    $this->sitePath = $site_path;
+    $this->enabledModules = $enabled_modules;
+    $this->keyValue = $key_value;
+  }
+
+  /**
+   * Gets all available update functions.
+   *
+   * @return callable[]
+   */
+  protected function getAvailableUpdateFunctions() {
+    $regexp = '/^(?<module>.+)_' . $this->updateName . '_(?<version>.+)$/';
+    $functions = get_defined_functions();
+
+    $updates = [];
+    foreach (preg_grep('/_' . $this->updateName . '_/', $functions['user']) as $function) {
+      // If this function is a module update function, add it to the list of
+      // module updates.
+      if (preg_match($regexp, $function, $matches)) {
+        $updates[] = $matches['module'] . '_' . $this->updateName . '_' . $matches['version'];
+      }
+    }
+
+    return $updates;
+  }
+
+  /**
+   * Gets the non already executed update functions.
+   *
+   * @return callable[]
+   */
+  public function getMissingUpdateFunctions() {
+    // We need a) the list of active modules (we get that from the config
+    // bootstrap factory) and b) the path to the modules, we use the extension
+    // discovery for that.
+
+    // Scan the module list.
+    // We don't support install profiles at that point?
+    $extension_discovery = new ExtensionDiscovery($this->root, FALSE, [], $this->sitePath);
+    $module_extensions = $extension_discovery->scan('module', FALSE);
+
+    $this->loadUpdateFiles($module_extensions);
+
+    // First figure out which hook_update got executed already.
+    $existing_update_functions = $this->keyValue->get('core.update__' . $this->updateName, []);
+
+    $available_update_functions = $this->getAvailableUpdateFunctions();
+    $not_executed_update_functions = array_diff($available_update_functions, $existing_update_functions);
+
+    return $not_executed_update_functions;
+  }
+
+  protected function loadUpdateFiles(array $module_extensions) {
+    // Load all the {$this->updateName}.inc files.
+    foreach ($this->enabledModules as $module) {
+      if (isset($module_extensions[$module])) {
+        $this->loadUpdateEarlyFile($module_extensions[$module]);
+      }
+    }
+  }
+
+  /**
+   * Loads the {$this->updateName}.inc file for a given extension.
+   *
+   * @param \Drupal\Core\Extension\Extension $module
+   */
+  protected function loadUpdateEarlyFile(Extension $module) {
+    $filename = $this->root . '/' . $module->getPath() . '/' . $module->getName() . ".{$this->updateName}.inc";
+    if (file_exists($filename)) {
+      include_once $filename;
+    }
+  }
+
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Update/UpdateRegistryTest.php b/core/tests/Drupal/Tests/Core/Update/UpdateRegistryTest.php
new file mode 100644
index 0000000..0fb1ae9
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Update/UpdateRegistryTest.php
@@ -0,0 +1,116 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Update\UpdateRegistryTest.
+ */
+
+namespace Drupal\Tests\Core\Update;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
+use Drupal\Core\Update\UpdateRegistry;
+use Drupal\Tests\UnitTestCase;
+use org\bovigo\vfs\vfsStream;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Update\UpdateRegistry
+ * @group Update
+ *
+ * @runTestsInSeparateProcesses
+ */
+class UpdateRegistryTest extends UnitTestCase {
+
+  protected function setupBasicModules($directories) {
+    $info_a = <<<'EOS'
+type: module
+name: Module A
+core: 8.x
+EOS;
+
+    $info_b = <<<'EOS'
+type: module
+name: Module B
+core: 8.x
+EOS;
+
+    $module_a = <<<'EOS'
+<?php
+
+function module_a_update_data_a() {
+}
+
+function module_a_update_data_b() {
+}
+
+EOS;
+    $module_b = <<<'EOS'
+<?php
+
+function module_b_update_data_a() {
+}
+
+EOS;
+    vfsStream::setup('drupal');
+    vfsStream::create(NestedArray::mergeDeep($directories, [
+      'sites' => [
+        'default' => [
+          'modules' => [
+            'module_a' => [
+              'module_a.update_data.inc' => $module_a,
+              'module_a.info.yml' => $info_a
+            ],
+            'module_b' => [
+              'module_b.update_data.inc' => $module_b,
+              'module_b.info.yml' => $info_b
+            ],
+          ]
+        ]
+      ],
+    ]));
+  }
+
+  /**
+   * @covers ::getMissingUpdateFunctions
+   */
+  public function testGetMissingUpdateFunctionsWithNoExistingUpdates() {
+    $this->setupBasicModules([]);
+
+    $key_value = $this->prophesize(KeyValueStoreInterface::class);
+    $key_value->get('core.update__update_data', [])->wilLReturn([]);
+    $key_value = $key_value->reveal();
+
+    $update_registry = new UpdateRegistry('vfs://drupal', 'sites/default', [
+      'module_a',
+      'module_b'
+    ], $key_value);
+
+    $this->assertEquals([
+      'module_a_update_data_a',
+      'module_a_update_data_b',
+      'module_b_update_data_a'
+    ], $update_registry->getMissingUpdateFunctions());
+  }
+
+  /**
+   * @covers ::getMissingUpdateFunctions
+   */
+  public function testGetMissingUpdateFunctionsWithExistingUpdates() {
+    $this->setupBasicModules([]);
+
+    $key_value = $this->prophesize(KeyValueStoreInterface::class);
+    $key_value->get('core.update__update_data', [])->wilLReturn(['module_a_update_data_a']);
+    $key_value = $key_value->reveal();
+
+    $update_registry = new UpdateRegistry('vfs://drupal', 'sites/default', [
+      'module_a',
+      'module_b'
+    ], $key_value);
+
+    $this->assertEquals(array_values([
+      'module_a_update_data_b',
+      'module_b_update_data_a'
+    ]), array_values($update_registry->getMissingUpdateFunctions()));
+  }
+
+}
