canonicalPathFor static method

String canonicalPathFor(
  1. String storagePath, {
  2. bool create = true,
})

How two spellings of one directory are recognised as one.

Without this, foo/bar and foo/./bar would take separate instances over the same files and diverge — the exact failure this class exists to prevent, reintroduced by a string comparison.

Lexical canonicalisation alone is NOT enough, and believing it was would leave the same hole open under a different spelling. p.canonicalize is pure string work: it does not follow symlinks, so a link and its target canonicalise to two different strings and would take two instances over one directory. That is not exotic — /data -> /mnt/data is an ordinary deployment, and on macOS Directory.systemTemp is /var/folders/... while its real path is /private/var/folders/....

So the directory is resolved on the filesystem, which needs it to exist: it is created first (idempotent, and Hive would create it moments later at box open anyway). Were it left to come into existence on its own, a caller arriving before it did would key on the unresolved spelling and a caller arriving after would key on the resolved one — two instances again.

create is false only on the close path, where a teardown verb must not write. Nothing is filed under a path that does not exist, so there the lexical form is enough to miss cleanly.

Falls back to the lexical form only if the filesystem refuses (a permission error, a path that cannot be a directory). That fallback restores the symlink hole for that path, so it logs rather than passing silently.

Implementation

static String canonicalPathFor(String storagePath, {bool create = true}) {
  // An empty path is not a location, and every step below would launder it
  // into one: createSync('') throws PathNotFoundException, which is a
  // FileSystemException, so the fallback catches it and p.canonicalize('')
  // hands back the PROCESS WORKING DIRECTORY. The server would then start
  // normally, rooted in its own image layer instead of the mounted volume,
  // and - because the secret file cannot be written there either, and that
  // failure is swallowed - with an UNENCRYPTED keystore. An unset
  // `${STORAGE_PATH}` in a compose or k8s manifest arrives here as '',
  // because Platform.environment reports an unset-but-interpolated variable
  // as present with an empty value.
  if (storagePath.trim().isEmpty) {
    throw ArgumentError.value(
        storagePath, 'storagePath', 'must not be empty or blank');
  }
  try {
    final dir = Directory(storagePath);
    if (!dir.existsSync()) {
      if (!create) {
        // Closing must not write. Nothing is filed under a path that does
        // not exist, so the lexical form is enough to miss cleanly.
        return p.canonicalize(storagePath);
      }
      dir.createSync(recursive: true);
    }
    return p.canonicalize(dir.resolveSymbolicLinksSync());
  } on FileSystemException catch (e) {
    _logger.warning('Could not resolve "$storagePath" on the filesystem'
        ' ($e); falling back to a lexical path. Two spellings of this'
        ' directory that differ by a symlink would now take separate Hive'
        ' instances over the same files.');
    return p.canonicalize(storagePath);
  }
}