When the data directory already holds a cluster (its PG_VERSION marker is
present) and PG_PASSWORD is not set, the bootstrap reads the superuser
password back from the install tree's password file (<install>/.pgpass, which
initdb was given) instead of generating a fresh one, so connections to the
reused cluster succeed. If that file is missing, unreadable, or empty the
bootstrap fails and names the data directory and the file: set PG_PASSWORD to
the password that initialized the cluster, or remove the stale cluster. An
explicit PG_PASSWORD always wins.
An explicit password short-circuits the whole check: the password file is not
read at all, so setting PG_PASSWORD is a working escape from a cluster whose
stored file has been lost.
When the data directory holds no cluster, a password file left in the install
tree by an earlier, reaped cluster is removed before the new cluster is
initialized. In releases after 0.6.0 this applies whether or not PG_PASSWORD
is set. initdb reads that file, so a stale one used to give the fresh cluster
the old password while the bootstrap reported a new one, and every process
after the first in a persistent install root (PG_EMBED_ROOT, or the default
per-user root) failed with password authentication failed. Under
cargo nextest, where each test is its own process, that was every test but
the first. A stale path that cannot be removed fails the bootstrap with
ClusterPasswordUnreadable rather than letting initdb read it.
With an explicit PG_DATA_DIR, the password file belongs to the install tree,
not to a data directory, so an install tree serves one such cluster at a time.
Point two clusters at one PG_RUNTIME_DIR with different PG_DATA_DIR values
and a fresh cluster in the second data directory removes the file the first
cluster's reuse depends on. Before this change, the fresh cluster was instead
initialized with the first cluster's password, and nobody could log in to it.
Give each cluster its own install root, for example its own PG_EMBED_ROOT, or
set PG_PASSWORD for both.
A failed connection to the admin database keeps the driver's error in the
chain, and the message names its cause, for example
failed to connect to admin database: db error: FATAL: password authentication
failed for user "postgres"
rather than a bare db error.
The file itself must be a regular file of no more than 4 KiB. The bootstrap
opens it once and takes both its type and its size from that one handle, so a
path replaced after a check cannot redirect the read. A FIFO, a directory, or
any other non-regular file is refused rather than waited on: the Unix open
carries O_NONBLOCK, so a FIFO with no writer returns at once and is then
rejected for its type. A file longer than the cap is refused without being
read. Both refusals return ClusterPasswordUnreadable, the error an unreadable
file returns.
Probing the data directory fails loudly as well. Only a missing PG_VERSION
marker means there is no cluster; a permission error, or any other failure to
read that marker, returns ClusterPasswordUnreadable rather than reporting no
cluster, so a data directory that is temporarily unsearchable cannot be
mistaken for a fresh one and started with a new password.
Three items at the crate root expose the same logic to a consumer that manages
its own Settings.
Table: Password-reuse API.
| Item | Purpose |
|---|---|
stored_cluster_password |
Query. Given the data directory and the password file, returns Ok(None) when the data directory holds no cluster, Ok(Some(password)) when it does, and an error when the data directory cannot be probed or when the file is missing, unreadable, empty, not a regular file, or larger than 4 KiB. |
reuse_existing_password |
Command. Takes the same two paths plus the mutable Settings and whether the caller supplied a password, aligns settings.password with the cluster on disk, and returns the outcome. |
PasswordReuseOutcome |
The bounded outcome of a successful call: Reused, ExplicitPassword, or NoCluster. Each is also an outcome label of the password_reuse tracing event, which carries four further labels for the failures. |
reuse_existing_password emits a warning-level password_reuse event on every
failure branch before the error is returned, labelled probe_failed,
missing_file, unreadable_file, or empty_file. A failure returns an error
rather than a PasswordReuseOutcome, so those four labels appear only in the
event: the field accepts all seven values, the returned enum carries the three
successes. No password is ever a label. stored_cluster_password emits nothing
at all: it is a query, so a caller that wants the event calls the command.
The same call records one Metric::PasswordReuse count, once per call and
never from the query. The crate takes no metrics dependency, because a library
should not choose one on a consumer's behalf; install a MetricsRecorder and
forward each count to whatever you already run.
use std::sync::Arc;
use pg_embedded_setup_unpriv::observability::{
Metric,
MetricsRecorder,
install_metrics_recorder,
};
struct Forwarding;
impl MetricsRecorder for Forwarding {
fn record(&self, metric: Metric) {
let Metric::PasswordReuse(outcome) = metric;
// `outcome` is an enum, so it is safe to use as a label directly.
println!("password_reuse {outcome:?}");
}
}
let _guard = install_metrics_recorder(Arc::new(Forwarding));
The outcome is an enum rather than a string, so the label set is bounded by
construction: a password or a path cannot reach a metric through it. The seven
values are Reused, ExplicitPassword and NoCluster for the success
branches, and ProbeFailed, MissingFile, UnreadableFile and EmptyFile
for the failures. ProbeFailed and UnreadableFile stay distinct even though
both return ClusterPasswordUnreadable. With no recorder installed, recording
costs a branch and a return.