|
1 | 1 | use anyhow::Result; |
2 | 2 | use include_env_compressed::{include_archive_from_env, Archive}; |
3 | | -use std::fs::{File, Permissions}; |
| 3 | +use sha2::{Digest, Sha256}; |
| 4 | +use std::fs; |
4 | 5 | use std::os::unix::fs::PermissionsExt; |
5 | | -use std::path::PathBuf; |
6 | | - |
7 | | -use tempfile::TempDir; |
8 | 6 |
|
9 | 7 | pub const KRANE_BIN: Archive = include_archive_from_env!("KRANE_PATH"); |
10 | 8 |
|
11 | | -lazy_static::lazy_static! { |
12 | | - pub static ref KRANE: Krane = Krane::seal().unwrap(); |
| 9 | +fn expected_checksum() -> String { |
| 10 | + let mut hasher = Sha256::new(); |
| 11 | + let mut reader = KRANE_BIN.reader(); |
| 12 | + std::io::copy(&mut reader, &mut hasher).unwrap(); |
| 13 | + format!("{:x}", hasher.finalize()) |
13 | 14 | } |
14 | 15 |
|
15 | | -#[derive(Debug)] |
16 | | -pub struct Krane { |
17 | | - // Hold the file in memory to keep the fd open |
18 | | - _tmp_dir: TempDir, |
19 | | - path: PathBuf, |
| 16 | +fn file_checksum(path: &std::path::Path) -> Result<String> { |
| 17 | + let mut hasher = Sha256::new(); |
| 18 | + let mut file = fs::File::open(path)?; |
| 19 | + std::io::copy(&mut file, &mut hasher)?; |
| 20 | + Ok(format!("{:x}", hasher.finalize())) |
20 | 21 | } |
21 | 22 |
|
22 | | -impl Krane { |
23 | | - fn seal() -> Result<Krane> { |
24 | | - let tmp_dir = TempDir::new()?; |
25 | | - let path = tmp_dir.path().join("krane"); |
| 23 | +pub fn ensure_krane_in_dir(tools_dir: impl AsRef<std::path::Path>) -> Result<std::path::PathBuf> { |
| 24 | + let tools_dir = tools_dir.as_ref(); |
| 25 | + let krane_path = tools_dir.join("krane"); |
26 | 26 |
|
27 | | - let mut krane_file = File::create(&path)?; |
28 | | - let permissions = Permissions::from_mode(0o755); |
29 | | - krane_file.set_permissions(permissions)?; |
| 27 | + let needs_update = if krane_path.exists() { |
| 28 | + match file_checksum(&krane_path) { |
| 29 | + Ok(actual) => actual != expected_checksum(), |
| 30 | + Err(_) => true, // If we can't read it, assume it needs update |
| 31 | + } |
| 32 | + } else { |
| 33 | + true |
| 34 | + }; |
30 | 35 |
|
31 | | - let mut krane_reader = KRANE_BIN.reader(); |
| 36 | + if needs_update { |
| 37 | + fs::create_dir_all(tools_dir)?; |
32 | 38 |
|
33 | | - std::io::copy(&mut krane_reader, &mut krane_file)?; |
| 39 | + let temp_path = tools_dir.join(format!(".krane.tmp.{}", std::process::id())); |
34 | 40 |
|
35 | | - Ok(Krane { |
36 | | - _tmp_dir: tmp_dir, |
37 | | - path, |
38 | | - }) |
39 | | - } |
| 41 | + { |
| 42 | + let mut temp_file = fs::File::create(&temp_path)?; |
| 43 | + let permissions = fs::Permissions::from_mode(0o755); |
| 44 | + temp_file.set_permissions(permissions)?; |
| 45 | + |
| 46 | + let mut krane_reader = KRANE_BIN.reader(); |
| 47 | + std::io::copy(&mut krane_reader, &mut temp_file)?; |
| 48 | + |
| 49 | + use std::io::Write; |
| 50 | + temp_file.flush()?; |
| 51 | + } |
40 | 52 |
|
41 | | - pub fn path(&self) -> &PathBuf { |
42 | | - &self.path |
| 53 | + fs::rename(temp_path, &krane_path)?; |
43 | 54 | } |
| 55 | + |
| 56 | + Ok(krane_path) |
44 | 57 | } |
45 | 58 |
|
46 | 59 | #[cfg(test)] |
47 | 60 | mod test { |
48 | 61 | use super::*; |
49 | | - use std::process::Command; |
50 | 62 |
|
51 | 63 | #[test] |
52 | | - fn test_krane_runs() { |
53 | | - let status = Command::new(KRANE.path()) |
| 64 | + fn test_checksum_functions() { |
| 65 | + let checksum1 = expected_checksum(); |
| 66 | + let checksum2 = expected_checksum(); |
| 67 | + assert_eq!(checksum1, checksum2); |
| 68 | + assert!(!checksum1.is_empty()); |
| 69 | + println!("Expected checksum: {}", checksum1); |
| 70 | + } |
| 71 | + |
| 72 | + #[test] |
| 73 | + fn test_ensure_krane_in_dir() { |
| 74 | + use tempfile::TempDir; |
| 75 | + |
| 76 | + let temp_dir = TempDir::new().unwrap(); |
| 77 | + println!("Testing ensure_krane_in_dir in: {:?}", temp_dir.path()); |
| 78 | + |
| 79 | + let path1 = crate::ensure_krane_in_dir(temp_dir.path()).unwrap(); |
| 80 | + assert!(path1.exists()); |
| 81 | + assert!(path1.ends_with("krane")); |
| 82 | + println!("Created krane at: {:?}", path1); |
| 83 | + |
| 84 | + let metadata = std::fs::metadata(&path1).unwrap(); |
| 85 | + let permissions = metadata.permissions(); |
| 86 | + assert!(permissions.mode() & 0o111 != 0); |
| 87 | + println!("File is executable: {}", permissions.mode() & 0o111 != 0); |
| 88 | + |
| 89 | + let mtime1 = std::fs::metadata(&path1).unwrap().modified().unwrap(); |
| 90 | + std::thread::sleep(std::time::Duration::from_millis(10)); |
| 91 | + |
| 92 | + let path2 = crate::ensure_krane_in_dir(temp_dir.path()).unwrap(); |
| 93 | + let mtime2 = std::fs::metadata(&path2).unwrap().modified().unwrap(); |
| 94 | + |
| 95 | + assert_eq!(path1, path2); |
| 96 | + assert_eq!(mtime1, mtime2); |
| 97 | + println!("File was reused (same mtime): {}", mtime1 == mtime2); |
| 98 | + |
| 99 | + let status = std::process::Command::new(&path1) |
54 | 100 | .arg("--help") |
55 | 101 | .output() |
56 | 102 | .expect("failed to run krane"); |
57 | | - |
58 | 103 | assert_eq!(status.status.code().unwrap(), 0); |
| 104 | + println!("Krane binary works correctly"); |
59 | 105 | } |
60 | 106 | } |
0 commit comments