oai-statsig-rust 0.27.0

Statsig Rust SDK for usage in multi-user server environments.
Documentation
use std::{
    fs::{create_dir_all, File},
    path::Path,
    time::UNIX_EPOCH,
};

#[cfg(unix)]
use std::os::unix::fs::{MetadataExt, PermissionsExt};
#[cfg(windows)]
use std::os::windows::io::AsRawHandle;

use crate::{log_w, StatsigErr};

use super::TAG;

#[derive(serde::Deserialize, serde::Serialize)]
struct MmapManifest {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    v1: Option<MmapArtifactIdentity>,
    v2: MmapArtifactIdentity,
}

#[derive(Eq, PartialEq, serde::Deserialize, serde::Serialize)]
struct MmapArtifactIdentity {
    len: u64,
    modified_secs: u64,
    modified_nanos: u32,
    #[cfg(unix)]
    device: u64,
    #[cfg(unix)]
    inode: u64,
    #[cfg(windows)]
    volume_serial_number: Option<u32>,
    #[cfg(windows)]
    file_index: Option<u64>,
}

impl MmapArtifactIdentity {
    fn from_file(file: &File) -> Result<Self, StatsigErr> {
        let metadata = file
            .metadata()
            .map_err(|error| StatsigErr::FileError(error.to_string()))?;
        let modified = metadata
            .modified()
            .map_err(|error| StatsigErr::FileError(error.to_string()))?
            .duration_since(UNIX_EPOCH)
            .map_err(|error| StatsigErr::FileError(error.to_string()))?;
        #[cfg(windows)]
        let (volume_serial_number, file_index) = windows_file_identity(file)?;

        Ok(Self {
            len: metadata.len(),
            modified_secs: modified.as_secs(),
            modified_nanos: modified.subsec_nanos(),
            #[cfg(unix)]
            device: metadata.dev(),
            #[cfg(unix)]
            inode: metadata.ino(),
            #[cfg(windows)]
            volume_serial_number: Some(volume_serial_number),
            #[cfg(windows)]
            file_index: Some(file_index),
        })
    }
}

#[cfg(windows)]
fn windows_file_identity(file: &File) -> Result<(u32, u64), StatsigErr> {
    use windows_sys::Win32::Storage::FileSystem::{
        GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION,
    };

    let mut information = unsafe { std::mem::zeroed::<BY_HANDLE_FILE_INFORMATION>() };
    let succeeded = unsafe { GetFileInformationByHandle(file.as_raw_handle(), &mut information) };
    if succeeded == 0 {
        return Err(StatsigErr::FileError(
            std::io::Error::last_os_error().to_string(),
        ));
    }

    let file_index =
        (u64::from(information.nFileIndexHigh) << 32) | u64::from(information.nFileIndexLow);
    Ok((information.dwVolumeSerialNumber, file_index))
}

pub(super) fn write_mmap_manifest(
    manifest_path: &Path,
    v1_file: Option<&File>,
    v2_file: &File,
) -> Result<(), StatsigErr> {
    let manifest = MmapManifest {
        v1: v1_file.map(MmapArtifactIdentity::from_file).transpose()?,
        v2: MmapArtifactIdentity::from_file(v2_file)?,
    };
    let mut file = new_manifest_temp_file(manifest_path)?;
    serde_json::to_writer(file.as_file_mut(), &manifest)
        .map_err(|error| StatsigErr::SerializationError(error.to_string()))?;
    sync_manifest_temp_file(&file)?;
    file.persist(manifest_path)
        .map(|_| ())
        .map_err(|error| StatsigErr::FileError(error.error.to_string()))
}

#[cfg(any(unix, windows))]
pub(super) fn open_committed_mmap_v2(
    manifest_path: &Path,
    v1_path: &Path,
    v2_path: &Path,
) -> Result<Option<File>, StatsigErr> {
    let v2_file = match File::open(v2_path) {
        Ok(file) => Some(file),
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,
        Err(error) => return Err(StatsigErr::FileError(error.to_string())),
    };
    let manifest_bytes = match std::fs::read(manifest_path) {
        Ok(bytes) => bytes,
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
            return if v2_file.is_some() {
                Err(incomplete_v2_publication())
            } else {
                Ok(None)
            };
        }
        Err(error) => return Err(StatsigErr::FileError(error.to_string())),
    };
    let manifest: MmapManifest = match serde_json::from_slice(&manifest_bytes) {
        Ok(manifest) => manifest,
        Err(error) => {
            log_w!(TAG, "Ignoring invalid mmap manifest: {error}");
            return Ok(None);
        }
    };
    let Some(v2_file) = v2_file else {
        return if manifest.v1.is_none() {
            Err(incomplete_v2_publication())
        } else {
            Ok(None)
        };
    };

    let current_v2 = MmapArtifactIdentity::from_file(&v2_file)?;
    if manifest.v2 != current_v2 {
        return if manifest.v1.is_none() {
            Err(incomplete_v2_publication())
        } else {
            Ok(None)
        };
    }

    if let Some(expected_v1) = manifest.v1 {
        let v1_file = match File::open(v1_path) {
            Ok(file) => file,
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
            Err(error) => return Err(StatsigErr::FileError(error.to_string())),
        };
        if expected_v1 != MmapArtifactIdentity::from_file(&v1_file)? {
            return Ok(None);
        }
    }

    Ok(Some(v2_file))
}

#[cfg(any(unix, windows))]
fn incomplete_v2_publication() -> StatsigErr {
    StatsigErr::InvalidOperation(
        "Interned mmap V2 publication is incomplete; retry preload".to_string(),
    )
}

#[cfg(not(any(unix, windows)))]
pub(super) fn open_committed_mmap_v2(
    _manifest_path: &Path,
    _v1_path: &Path,
    _v2_path: &Path,
) -> Result<Option<File>, StatsigErr> {
    Ok(None)
}

fn new_manifest_temp_file(path: &Path) -> Result<tempfile::NamedTempFile, StatsigErr> {
    let parent = path
        .parent()
        .ok_or_else(|| StatsigErr::FileError("Mmap manifest path has no parent".to_string()))?;
    create_dir_all(parent).map_err(|error| StatsigErr::FileError(error.to_string()))?;
    tempfile::NamedTempFile::new_in(parent)
        .map_err(|error| StatsigErr::FileError(error.to_string()))
}

fn sync_manifest_temp_file(file: &tempfile::NamedTempFile) -> Result<(), StatsigErr> {
    #[cfg(unix)]
    file.as_file()
        .set_permissions(std::fs::Permissions::from_mode(0o644))
        .map_err(|error| StatsigErr::FileError(error.to_string()))?;
    file.as_file()
        .sync_all()
        .map_err(|error| StatsigErr::FileError(error.to_string()))
}

#[cfg(test)]
pub(crate) fn write_mmap_manifest_for_test(
    manifest_path: &Path,
    v1_path: &Path,
    v2_path: &Path,
) -> Result<(), StatsigErr> {
    let v1_file = File::open(v1_path).map_err(|error| StatsigErr::FileError(error.to_string()))?;
    let v2_file = File::open(v2_path).map_err(|error| StatsigErr::FileError(error.to_string()))?;
    write_mmap_manifest(manifest_path, Some(&v1_file), &v2_file)
}

#[cfg(test)]
pub(crate) fn write_mmap_v2_only_manifest_for_test(
    manifest_path: &Path,
    v2_path: &Path,
) -> Result<(), StatsigErr> {
    let v2_file = File::open(v2_path).map_err(|error| StatsigErr::FileError(error.to_string()))?;
    write_mmap_manifest(manifest_path, None, &v2_file)
}

#[cfg(all(test, any(unix, windows)))]
pub(crate) fn open_committed_mmap_v2_for_test(
    manifest_path: &Path,
    v1_path: &Path,
    v2_path: &Path,
) -> Result<Option<File>, StatsigErr> {
    open_committed_mmap_v2(manifest_path, v1_path, v2_path)
}

#[cfg(all(test, any(unix, windows)))]
mod tests {
    use super::*;

    #[test]
    fn manifest_from_persisted_handles_rejects_replaced_path() {
        let directory = tempfile::tempdir().unwrap();
        let v1_path = directory.path().join("v1.mmap");
        let v2_path = directory.path().join("v2.mmap");
        let manifest_path = directory.path().join("manifest.json");

        let v1_temp = tempfile::NamedTempFile::new_in(directory.path()).unwrap();
        std::fs::write(v1_temp.path(), b"writer v1").unwrap();
        let v1_file = v1_temp.persist(&v1_path).unwrap();
        let v2_temp = tempfile::NamedTempFile::new_in(directory.path()).unwrap();
        std::fs::write(v2_temp.path(), b"writer v2").unwrap();
        let v2_file = v2_temp.persist(&v2_path).unwrap();

        let replacement = tempfile::NamedTempFile::new_in(directory.path()).unwrap();
        std::fs::write(replacement.path(), b"legacy writer v1").unwrap();
        replacement.persist(&v1_path).unwrap();

        write_mmap_manifest(&manifest_path, Some(&v1_file), &v2_file).unwrap();
        assert!(open_committed_mmap_v2(&manifest_path, &v1_path, &v2_path)
            .unwrap()
            .is_none());
    }

    #[test]
    fn v2_only_manifest_does_not_require_v1_artifact() {
        let directory = tempfile::tempdir().unwrap();
        let v1_path = directory.path().join("missing-v1.mmap");
        let v2_path = directory.path().join("v2.mmap");
        let manifest_path = directory.path().join("manifest.json");

        let v2_temp = tempfile::NamedTempFile::new_in(directory.path()).unwrap();
        std::fs::write(v2_temp.path(), b"writer v2").unwrap();
        let v2_file = v2_temp.persist(&v2_path).unwrap();
        write_mmap_manifest(&manifest_path, None, &v2_file).unwrap();

        assert!(open_committed_mmap_v2(&manifest_path, &v1_path, &v2_path)
            .unwrap()
            .is_some());
    }

    #[test]
    fn v2_only_manifest_rejects_replaced_v2_artifact() {
        let directory = tempfile::tempdir().unwrap();
        let v1_path = directory.path().join("stale-v1.mmap");
        let v2_path = directory.path().join("v2.mmap");
        let manifest_path = directory.path().join("manifest.json");

        std::fs::write(&v1_path, b"stale v1").unwrap();
        let v2_temp = tempfile::NamedTempFile::new_in(directory.path()).unwrap();
        std::fs::write(v2_temp.path(), b"writer v2").unwrap();
        let v2_file = v2_temp.persist(&v2_path).unwrap();
        write_mmap_manifest(&manifest_path, None, &v2_file).unwrap();

        let replacement = tempfile::NamedTempFile::new_in(directory.path()).unwrap();
        std::fs::write(replacement.path(), b"next writer v2").unwrap();
        replacement.persist(&v2_path).unwrap();

        assert!(matches!(
            open_committed_mmap_v2(&manifest_path, &v1_path, &v2_path),
            Err(StatsigErr::InvalidOperation(message))
                if message == "Interned mmap V2 publication is incomplete; retry preload"
        ));
    }

    #[test]
    fn v2_without_manifest_requires_retry() {
        let directory = tempfile::tempdir().unwrap();
        let v1_path = directory.path().join("stale-v1.mmap");
        let v2_path = directory.path().join("v2.mmap");
        let manifest_path = directory.path().join("missing-manifest.json");
        std::fs::write(&v1_path, b"stale v1").unwrap();
        std::fs::write(&v2_path, b"writer v2").unwrap();

        assert!(matches!(
            open_committed_mmap_v2(&manifest_path, &v1_path, &v2_path),
            Err(StatsigErr::InvalidOperation(message))
                if message == "Interned mmap V2 publication is incomplete; retry preload"
        ));
    }
}