Avoid TOCTOU errors in .python-version reads (#5223)

## Summary

Not a big deal, but better to try the operation and handle the failure
case than to check if the file exists and _then_ read it.
This commit is contained in:
Charlie Marsh 2024-07-19 11:08:20 -04:00 committed by GitHub
parent ed9b820815
commit 2169902bd9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 21 additions and 33 deletions

View file

@ -14,8 +14,8 @@ pub use crate::prefix::Prefix;
pub use crate::python_version::PythonVersion; pub use crate::python_version::PythonVersion;
pub use crate::target::Target; pub use crate::target::Target;
pub use crate::version_files::{ pub use crate::version_files::{
request_from_version_file, requests_from_version_file, version_file_exists, request_from_version_file, requests_from_version_file, PYTHON_VERSIONS_FILENAME,
versions_file_exists, PYTHON_VERSIONS_FILENAME, PYTHON_VERSION_FILENAME, PYTHON_VERSION_FILENAME,
}; };
pub use crate::virtualenv::{Error as VirtualEnvError, PyVenvConfiguration, VirtualEnvironment}; pub use crate::virtualenv::{Error as VirtualEnvError, PyVenvConfiguration, VirtualEnvironment};
mod discovery; mod discovery;

View file

@ -1,5 +1,4 @@
use fs_err as fs; use fs_err as fs;
use std::{io, path::PathBuf};
use tracing::debug; use tracing::debug;
use crate::PythonRequest; use crate::PythonRequest;
@ -14,7 +13,7 @@ pub static PYTHON_VERSIONS_FILENAME: &str = ".python-versions";
/// ///
/// Prefers `.python-versions` then `.python-version`. /// Prefers `.python-versions` then `.python-version`.
/// If only one Python version is desired, use [`request_from_version_files`] which prefers the `.python-version` file. /// If only one Python version is desired, use [`request_from_version_files`] which prefers the `.python-version` file.
pub async fn requests_from_version_file() -> Result<Option<Vec<PythonRequest>>, io::Error> { pub async fn requests_from_version_file() -> Result<Option<Vec<PythonRequest>>, std::io::Error> {
if let Some(versions) = read_versions_file().await? { if let Some(versions) = read_versions_file().await? {
Ok(Some( Ok(Some(
versions versions
@ -33,49 +32,38 @@ pub async fn requests_from_version_file() -> Result<Option<Vec<PythonRequest>>,
/// ///
/// Prefers `.python-version` then the first entry of `.python-versions`. /// Prefers `.python-version` then the first entry of `.python-versions`.
/// If multiple Python versions are desired, use [`requests_from_version_files`] instead. /// If multiple Python versions are desired, use [`requests_from_version_files`] instead.
pub async fn request_from_version_file() -> Result<Option<PythonRequest>, io::Error> { pub async fn request_from_version_file() -> Result<Option<PythonRequest>, std::io::Error> {
if let Some(version) = read_version_file().await? { if let Some(version) = read_version_file().await? {
Ok(Some(PythonRequest::parse(&version))) Ok(Some(PythonRequest::parse(&version)))
} else if let Some(versions) = read_versions_file().await? { } else if let Some(versions) = read_versions_file().await? {
Ok(versions Ok(versions
.into_iter() .into_iter()
.next() .next()
.inspect(|_| debug!("Using the first version from `.python-versions`")) .inspect(|_| debug!("Using the first version from `{PYTHON_VERSIONS_FILENAME}`"))
.map(|version| PythonRequest::parse(&version))) .map(|version| PythonRequest::parse(&version)))
} else { } else {
Ok(None) Ok(None)
} }
} }
pub fn versions_file_exists() -> Result<bool, io::Error> { async fn read_versions_file() -> Result<Option<Vec<String>>, std::io::Error> {
PathBuf::from(PYTHON_VERSIONS_FILENAME).try_exists() match fs::tokio::read_to_string(PYTHON_VERSIONS_FILENAME).await {
} Ok(content) => {
debug!("Reading requests from `{PYTHON_VERSIONS_FILENAME}`");
async fn read_versions_file() -> Result<Option<Vec<String>>, io::Error> { Ok(Some(content.lines().map(ToString::to_string).collect()))
if !versions_file_exists()? { }
return Ok(None); Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
} }
debug!("Reading requests from `{PYTHON_VERSIONS_FILENAME}`");
let lines: Vec<String> = fs::tokio::read_to_string(PYTHON_VERSIONS_FILENAME)
.await?
.lines()
.map(ToString::to_string)
.collect();
Ok(Some(lines))
} }
pub fn version_file_exists() -> Result<bool, io::Error> { async fn read_version_file() -> Result<Option<String>, std::io::Error> {
PathBuf::from(PYTHON_VERSION_FILENAME).try_exists() match fs::tokio::read_to_string(PYTHON_VERSION_FILENAME).await {
} Ok(content) => {
debug!("Reading requests from `{PYTHON_VERSION_FILENAME}`");
async fn read_version_file() -> Result<Option<String>, io::Error> { Ok(content.lines().next().map(ToString::to_string))
if !version_file_exists()? { }
return Ok(None); Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
} }
debug!("Reading requests from `{PYTHON_VERSION_FILENAME}`");
Ok(fs::tokio::read_to_string(PYTHON_VERSION_FILENAME)
.await?
.lines()
.next()
.map(ToString::to_string))
} }