in sources/api/migration/migrator/src/main.rs [340:490]
fn flip_to_new_version<P>(version: &Version, to_datastore: P) -> Result<()>
where
P: AsRef<Path>,
{
// Get the directory we're working in.
let to_dir = to_datastore
.as_ref()
.parent()
.context(error::DataStoreLinkToRoot {
path: to_datastore.as_ref(),
})?;
// We need a file descriptor for the directory so we can fsync after the symlink swap.
let raw_dir = Dir::open(
to_dir,
// Confirm it's a directory
OFlag::O_DIRECTORY,
// (mode doesn't matter for opening a directory)
Mode::empty(),
)
.context(error::DataStoreDirOpen { path: &to_dir })?;
// Get a unique temporary path in the directory; we need this to atomically swap.
let temp_link = to_dir.join(rando());
// Build the path to the 'current' link; this is what we're atomically swapping from
// pointing at the old major version to pointing at the new major version.
// Example: /path/to/datastore/current
let current_version_link = to_dir.join("current");
// Build the path to the major version link; this is what we're atomically swapping from
// pointing at the old minor version to pointing at the new minor version.
// Example: /path/to/datastore/v1
let major_version_link = to_dir.join(format!("v{}", version.major));
// Build the path to the minor version link; this is what we're atomically swapping from
// pointing at the old patch version to pointing at the new patch version.
// Example: /path/to/datastore/v1.5
let minor_version_link = to_dir.join(format!("v{}.{}", version.major, version.minor));
// Build the path to the patch version link. If this already exists, it's because we've
// previously tried to migrate to this version. We point it at the full `to_datastore`
// path.
// Example: /path/to/datastore/v1.5.2
let patch_version_link = to_dir.join(format!(
"v{}.{}.{}",
version.major, version.minor, version.patch
));
// Get the final component of the paths we're linking to, so we can use relative links instead
// of absolute, for understandability.
let to_target = to_datastore
.as_ref()
.file_name()
.context(error::DataStoreLinkToRoot {
path: to_datastore.as_ref(),
})?;
let patch_target = patch_version_link
.file_name()
.context(error::DataStoreLinkToRoot {
path: to_datastore.as_ref(),
})?;
let minor_target = minor_version_link
.file_name()
.context(error::DataStoreLinkToRoot {
path: to_datastore.as_ref(),
})?;
let major_target = major_version_link
.file_name()
.context(error::DataStoreLinkToRoot {
path: to_datastore.as_ref(),
})?;
// =^..^= =^..^= =^..^= =^..^=
info!(
"Flipping {} to point to {}",
patch_version_link.display(),
to_target.to_string_lossy(),
);
// Create a symlink from the patch version to the new data store. We create it at a temporary
// path so we can atomically swap it into the real path with a rename call.
// This will point at, for example, /path/to/datastore/v1.5.2_0123456789abcdef
symlink(&to_target, &temp_link).context(error::LinkCreate { path: &temp_link })?;
// Atomically swap the link into place, so that the patch version link points to the new data
// store copy.
fs::rename(&temp_link, &patch_version_link).context(error::LinkSwap {
link: &patch_version_link,
})?;
// =^..^= =^..^= =^..^= =^..^=
info!(
"Flipping {} to point to {}",
minor_version_link.display(),
patch_target.to_string_lossy(),
);
// Create a symlink from the minor version to the new patch version.
// This will point at, for example, /path/to/datastore/v1.5.2
symlink(&patch_target, &temp_link).context(error::LinkCreate { path: &temp_link })?;
// Atomically swap the link into place, so that the minor version link points to the new patch
// version.
fs::rename(&temp_link, &minor_version_link).context(error::LinkSwap {
link: &minor_version_link,
})?;
// =^..^= =^..^= =^..^= =^..^=
info!(
"Flipping {} to point to {}",
major_version_link.display(),
minor_target.to_string_lossy(),
);
// Create a symlink from the major version to the new minor version.
// This will point at, for example, /path/to/datastore/v1.5
symlink(&minor_target, &temp_link).context(error::LinkCreate { path: &temp_link })?;
// Atomically swap the link into place, so that the major version link points to the new minor
// version.
fs::rename(&temp_link, &major_version_link).context(error::LinkSwap {
link: &major_version_link,
})?;
// =^..^= =^..^= =^..^= =^..^=
info!(
"Flipping {} to point to {}",
current_version_link.display(),
major_target.to_string_lossy(),
);
// Create a symlink from 'current' to the new major version.
// This will point at, for example, /path/to/datastore/v1
symlink(&major_target, &temp_link).context(error::LinkCreate { path: &temp_link })?;
// Atomically swap the link into place, so that 'current' points to the new major version.
fs::rename(&temp_link, ¤t_version_link).context(error::LinkSwap {
link: ¤t_version_link,
})?;
// =^..^= =^..^= =^..^= =^..^=
// fsync the directory so the links point to the new version even if we crash right after
// this. If fsync fails, warn but continue, because we likely can't swap the links back
// without hitting the same failure.
fsync(raw_dir.as_raw_fd()).unwrap_or_else(|e| {
warn!(
"fsync of data store directory '{}' failed, update may disappear if we crash now: {}",
to_dir.display(),
e
)
});
Ok(())
}