#
cargo/sources/registry/
local.rs

    1//! Access to a registry on the local filesystem. See [`LocalRegistry`] for more.
    2
    3use crate::core::PackageId;
    4use crate::sources::registry::{LoadResponse, MaybeLock, RegistryConfig, RegistryData};
    5use crate::util::errors::CargoResult;
    6use crate::util::{Filesystem, GlobalContext};
    7use cargo_util::{Sha256, paths};
    8use std::fs::File;
    9use std::io::SeekFrom;
    10use std::io::{self, prelude::*};
    11use std::path::Path;
    12use std::task::Poll;
    13
    14/// A local registry is a registry that lives on the filesystem as a set of
    15/// `.crate` files with an `index` directory in the [same format] as a remote
    16/// registry.
    17///
    18/// This type is primarily accessed through the [`RegistryData`] trait.
    19///
    20/// When a local registry is requested for a package, it simply looks into what
    21/// its index has under the `index` directory. When [`LocalRegistry::download`]
    22/// is called, a local registry verifies the checksum of the requested `.crate`
    23/// tarball and then unpacks it to `$CARGO_HOME/.registry/src`.
    24///
    25/// > Note that there is a third-party subcommand [`cargo-local-registry`],
    26/// > which happened to be developed by a former Cargo team member when local
    27/// > registry was introduced. The tool is to ease the burden of maintaining
    28/// > local registries. However, in general the Cargo team avoids recommending
    29/// > any specific third-party crate. Just FYI.
    30///
    31/// [same format]: super#the-format-of-the-index
    32/// [`cargo-local-registry`]: https://crates.io/crates/cargo-local-registry
    33///
    34/// # Filesystem hierarchy
    35///
    36/// Here is an example layout of a local registry on a local filesystem:
    37///
    38/// ```text
    39/// [registry root]/
    40/// ├── index/                      # registry index
    41/// │  ├── an/
    42/// │  │  └── yh/
    43/// │  │     └── anyhow
    44/// │  ├── ru/
    45/// │  │  └── st/
    46/// │  │     ├── rustls
    47/// │  │     └── rustls-ffi
    48/// │  └── se/
    49/// │     └── mv/
    50/// │        └── semver
    51/// ├── anyhow-1.0.71.crate         # pre-downloaded crate tarballs
    52/// ├── rustls-0.20.8.crate
    53/// ├── rustls-ffi-0.8.2.crate
    54/// └── semver-1.0.17.crate
    55/// ```
    56///
    57/// For general concepts of registries, see the [module-level documentation](crate::sources::registry).
    58pub struct LocalRegistry<'gctx> {
    59    /// Path to the registry index.
    60    index_path: Filesystem,
    61    /// Root path of this local registry.
    62    root: Filesystem,
    63    /// Path where this local registry extract `.crate` tarballs to.
    64    src_path: Filesystem,
    65    gctx: &'gctx GlobalContext,
    66    /// Whether this source has updated all package information it may contain.
    67    updated: bool,
    68    /// Disables status messages.
    69    quiet: bool,
    70}
    71
    72impl<'gctx> LocalRegistry<'gctx> {
    73    /// Creates a local registry at `root`.
    74    ///
    75    /// * `name` --- Name of a path segment where `.crate` tarballs are stored.
    76    ///   Expect to be unique.
    77    pub fn new(root: &Path, gctx: &'gctx GlobalContext, name: &str) -> LocalRegistry<'gctx> {
    78        LocalRegistry {
    79            src_path: gctx.registry_source_path().join(name),
    80            index_path: Filesystem::new(root.join("index")),
    81            root: Filesystem::new(root.to_path_buf()),
    82            gctx,
    83            updated: false,
    84            quiet: false,
    85        }
    86    }
    87}
    88
    89impl<'gctx> RegistryData for LocalRegistry<'gctx> {
    90    fn prepare(&self) -> CargoResult<()> {
    91        Ok(())
    92    }
    93
    94    fn index_path(&self) -> &Filesystem {
    95        &self.index_path
    96    }
    97
    98    fn cache_path(&self) -> &Filesystem {
    99        &self.root
    100    }
    101
    102    fn assert_index_locked<'a>(&self, path: &'a Filesystem) -> &'a Path {
    103        // Note that the `*_unlocked` variant is used here since we're not
    104        // modifying the index and it's required to be externally synchronized.
    105        path.as_path_unlocked()
    106    }
    107
    108    fn load(
    109        &mut self,
    110        root: &Path,
    111        path: &Path,
    112        _index_version: Option<&str>,
    113    ) -> Poll<CargoResult<LoadResponse>> {
    114        if self.updated {
    115            let raw_data = match paths::read_bytes(&root.join(path)) {
    116                Err(e)
    117                    if e.downcast_ref::<io::Error>()
    118                        .map_or(false, |ioe| ioe.kind() == io::ErrorKind::NotFound) =>
    119                {
    120                    return Poll::Ready(Ok(LoadResponse::NotFound));
    121                }
    122                r => r,
    123            }?;
    124            Poll::Ready(Ok(LoadResponse::Data {
    125                raw_data,
    126                index_version: None,
    127            }))
    128        } else {
    129            Poll::Pending
    130        }
    131    }
    132
    133    fn config(&mut self) -> Poll<CargoResult<Option<RegistryConfig>>> {
    134        // Local registries don't have configuration for remote APIs or anything
    135        // like that
    136        Poll::Ready(Ok(None))
    137    }
    138
    139    fn block_until_ready(&mut self) -> CargoResult<()> {
    140        if self.updated {
    141            return Ok(());
    142        }
    143        // Nothing to update, we just use what's on disk. Verify it actually
    144        // exists though. We don't use any locks as we're just checking whether
    145        // these directories exist.
    146        let root = self.root.clone().into_path_unlocked();
    147        if !root.is_dir() {
    148            anyhow::bail!("local registry path is not a directory: {}", root.display());
    149        }
    150        let index_path = self.index_path.clone().into_path_unlocked();
    151        if !index_path.is_dir() {
    152            anyhow::bail!(
    153                "local registry index path is not a directory: {}",
    154                index_path.display()
    155            );
    156        }
    157        self.updated = true;
    158        Ok(())
    159    }
    160
    161    fn invalidate_cache(&mut self) {
    162        // Local registry has no cache - just reads from disk.
    163    }
    164
    165    fn set_quiet(&mut self, _quiet: bool) {
    166        self.quiet = true;
    167    }
    168
    169    fn is_updated(&self) -> bool {
    170        self.updated
    171    }
    172
    173    fn download(&mut self, pkg: PackageId, checksum: &str) -> CargoResult<MaybeLock> {
    174        // Note that the usage of `into_path_unlocked` here is because the local
    175        // crate files here never change in that we're not the one writing them,
    176        // so it's not our responsibility to synchronize access to them.
    177        let path = self.root.join(&pkg.tarball_name()).into_path_unlocked();
    178        let mut crate_file = paths::open(&path)?;
    179
    180        // If we've already got an unpacked version of this crate, then skip the
    181        // checksum below as it is in theory already verified.
    182        let dst = path.file_stem().unwrap();
    183        if self.src_path.join(dst).into_path_unlocked().exists() {
    184            return Ok(MaybeLock::Ready(crate_file));
    185        }
    186
    187        if !self.quiet {
    188            self.gctx.shell().status("Unpacking", pkg)?;
    189        }
    190
    191        // We don't actually need to download anything per-se, we just need to
    192        // verify the checksum matches the .crate file itself.
    193        let actual = Sha256::new().update_file(&crate_file)?.finish_hex();
    194        if actual != checksum {
    195            anyhow::bail!("failed to verify the checksum of `{}`", pkg)
    196        }
    197
    198        crate_file.seek(SeekFrom::Start(0))?;
    199
    200        Ok(MaybeLock::Ready(crate_file))
    201    }
    202
    203    fn finish_download(
    204        &mut self,
    205        _pkg: PackageId,
    206        _checksum: &str,
    207        _data: &[u8],
    208    ) -> CargoResult<File> {
    209        panic!("this source doesn't download")
    210    }
    211}