gatehouse_runtime_protocol/lifecycle/journal/storage/slot/
mapping.rs1use super::*;
3use sha2::{Digest, Sha256};
4fn name(project: &str, resource: &str) -> Result<String> {
5 crate::nonempty(project)?;
6 crate::nonempty(resource)?;
7 let mut hash = Sha256::new();
8 for value in ["gatehouse-lifecycle-resource/v1", project, resource] {
9 hash.update((value.len() as u64).to_be_bytes());
10 hash.update(value.as_bytes());
11 }
12 Ok(format!("resource-{:x}", hash.finalize()))
13}
14impl ServiceSlot {
15 pub fn mapped_root(base: &Path, project: &str, resource: &str) -> Result<PathBuf> {
18 Ok(base.join(name(project, resource)?))
19 }
20 pub fn acquire_mapped(
21 base: &Path,
22 resolve: Request,
23 plan: CreatePlan,
24 healthcheck: Vec<String>,
25 ) -> Result<Self> {
26 resolve.validate()?;
27 let parent = Journal::directory(base)?;
28 let name = name(&resolve.binding.project_id, &resolve.binding.resource_id)?;
29 let child = CString::new(name.clone()).unwrap();
30 if unsafe { libc::mkdirat(parent.as_raw_fd(), child.as_ptr(), 0o700) } != 0
33 && std::io::Error::last_os_error().kind() != std::io::ErrorKind::AlreadyExists
34 {
35 return Err(Invalid("cannot provision lifecycle resource root"));
36 }
37 io(parent.sync_all())?;
38 let root = base.join(name);
39 let slot = Self::acquire(&root, resolve, plan, healthcheck)?;
40 slot.check()?;
41 Ok(slot)
42 }
43}
44
45impl ServiceSlot {
46 pub fn project_state_base(project: &Path) -> Result<PathBuf> {
49 if !project.is_absolute() || io(fs::canonicalize(project))? != project {
50 return Err(Invalid("project state root must be canonical"));
51 }
52 for path in project.ancestors() {
53 let m = io(fs::symlink_metadata(path))?;
54 if !m.is_dir()
55 || m.mode() & 0o022 != 0
56 || ![0, unsafe { libc::geteuid() }].contains(&m.uid())
57 {
58 return Err(Invalid("unprotected project state ancestor"));
59 }
60 }
61 let mut directory = io(OpenOptions::new()
62 .read(true)
63 .custom_flags(libc::O_DIRECTORY | libc::O_NOFOLLOW | libc::O_CLOEXEC)
64 .open(project))?;
65 let mut path = project.to_path_buf();
66 for component in [".harness-gate", "lifecycle-services"] {
67 let name = CString::new(component).unwrap();
68 if unsafe { libc::mkdirat(directory.as_raw_fd(), name.as_ptr(), 0o700) } != 0
69 && std::io::Error::last_os_error().kind() != std::io::ErrorKind::AlreadyExists
70 {
71 return Err(Invalid("cannot provision fixed lifecycle state"));
72 }
73 io(directory.sync_all())?;
74 let fd = unsafe {
75 libc::openat(
76 directory.as_raw_fd(),
77 name.as_ptr(),
78 libc::O_RDONLY | libc::O_DIRECTORY | libc::O_NOFOLLOW | libc::O_CLOEXEC,
79 )
80 };
81 if fd < 0 {
82 return Err(Invalid("invalid lifecycle state directory"));
83 }
84 let child = unsafe { File::from_raw_fd(fd) };
85 let m = io(child.metadata())?;
86 if m.uid() != unsafe { libc::geteuid() } || m.mode() & 0o022 != 0 {
87 return Err(Invalid("unprotected lifecycle state directory"));
88 }
89 path.push(component);
90 directory = child;
91 }
92 let verified = Journal::directory(&path)?;
94 let actual = io(verified.metadata())?;
95 let held = io(directory.metadata())?;
96 if (actual.dev(), actual.ino()) != (held.dev(), held.ino()) {
97 return Err(Invalid("lifecycle state directory changed"));
98 }
99 Ok(path)
100 }
101}