From 2169902bd9b245b16a2ec30cd4e6e31c6461aeec Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 19 Jul 2024 11:08:20 -0400 Subject: [PATCH] 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. --- crates/uv-python/src/lib.rs | 4 +-- crates/uv-python/src/version_files.rs | 50 ++++++++++----------------- 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs index cc59132f6..8c429ad57 100644 --- a/crates/uv-python/src/lib.rs +++ b/crates/uv-python/src/lib.rs @@ -14,8 +14,8 @@ pub use crate::prefix::Prefix; pub use crate::python_version::PythonVersion; pub use crate::target::Target; pub use crate::version_files::{ - request_from_version_file, requests_from_version_file, version_file_exists, - versions_file_exists, PYTHON_VERSIONS_FILENAME, PYTHON_VERSION_FILENAME, + request_from_version_file, requests_from_version_file, PYTHON_VERSIONS_FILENAME, + PYTHON_VERSION_FILENAME, }; pub use crate::virtualenv::{Error as VirtualEnvError, PyVenvConfiguration, VirtualEnvironment}; mod discovery; diff --git a/crates/uv-python/src/version_files.rs b/crates/uv-python/src/version_files.rs index 7a8d82a91..7e9f9db22 100644 --- a/crates/uv-python/src/version_files.rs +++ b/crates/uv-python/src/version_files.rs @@ -1,5 +1,4 @@ use fs_err as fs; -use std::{io, path::PathBuf}; use tracing::debug; use crate::PythonRequest; @@ -14,7 +13,7 @@ pub static PYTHON_VERSIONS_FILENAME: &str = ".python-versions"; /// /// Prefers `.python-versions` then `.python-version`. /// 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>, io::Error> { +pub async fn requests_from_version_file() -> Result>, std::io::Error> { if let Some(versions) = read_versions_file().await? { Ok(Some( versions @@ -33,49 +32,38 @@ pub async fn requests_from_version_file() -> Result>, /// /// Prefers `.python-version` then the first entry of `.python-versions`. /// If multiple Python versions are desired, use [`requests_from_version_files`] instead. -pub async fn request_from_version_file() -> Result, io::Error> { +pub async fn request_from_version_file() -> Result, std::io::Error> { if let Some(version) = read_version_file().await? { Ok(Some(PythonRequest::parse(&version))) } else if let Some(versions) = read_versions_file().await? { Ok(versions .into_iter() .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))) } else { Ok(None) } } -pub fn versions_file_exists() -> Result { - PathBuf::from(PYTHON_VERSIONS_FILENAME).try_exists() -} - -async fn read_versions_file() -> Result>, io::Error> { - if !versions_file_exists()? { - return Ok(None); +async fn read_versions_file() -> Result>, std::io::Error> { + match fs::tokio::read_to_string(PYTHON_VERSIONS_FILENAME).await { + Ok(content) => { + debug!("Reading requests from `{PYTHON_VERSIONS_FILENAME}`"); + Ok(Some(content.lines().map(ToString::to_string).collect())) + } + 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 = fs::tokio::read_to_string(PYTHON_VERSIONS_FILENAME) - .await? - .lines() - .map(ToString::to_string) - .collect(); - Ok(Some(lines)) } -pub fn version_file_exists() -> Result { - PathBuf::from(PYTHON_VERSION_FILENAME).try_exists() -} - -async fn read_version_file() -> Result, io::Error> { - if !version_file_exists()? { - return Ok(None); +async fn read_version_file() -> Result, std::io::Error> { + match fs::tokio::read_to_string(PYTHON_VERSION_FILENAME).await { + Ok(content) => { + debug!("Reading requests from `{PYTHON_VERSION_FILENAME}`"); + Ok(content.lines().next().map(ToString::to_string)) + } + 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)) }