coding_agent_search/pages/
mod.rs1use anyhow::{Context, Result, bail};
2use frankensqlite::Connection;
3use frankensqlite::compat::OpenFlags;
4use std::fs::Metadata;
5#[cfg(not(windows))]
6use std::fs::OpenOptions;
7#[cfg(not(windows))]
8use std::io::Write;
9use std::path::{Path, PathBuf};
10
11pub mod analytics;
12pub mod archive_config;
13pub mod attachments;
14pub mod bundle;
15pub mod config_input;
16pub mod confirmation;
17pub mod deploy_cloudflare;
18pub mod deploy_github;
19pub mod docs;
20pub mod encrypt;
21pub mod errors;
22pub mod export;
23pub mod fts;
24pub mod key_management;
25pub mod password;
26pub mod patterns;
27pub mod preview;
28pub mod profiles;
29pub mod qr;
30pub mod redact;
31pub mod secret_scan;
32pub mod size;
33pub mod summary;
34pub mod verify;
35pub mod wizard;
36
37fn ensure_real_directory(path: &Path, metadata: &Metadata, label: &str) -> Result<()> {
38 let file_type = metadata.file_type();
39 if file_type.is_symlink() {
40 bail!("{label} must not be a symlink: {}", path.display());
41 }
42 if !file_type.is_dir() {
43 bail!("{label} must be a directory: {}", path.display());
44 }
45 Ok(())
46}
47
48pub(crate) fn resolve_site_dir(path: &Path) -> Result<PathBuf> {
49 let path_metadata = match std::fs::symlink_metadata(path) {
50 Ok(metadata) => metadata,
51 Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
52 bail!("path does not exist: {}", path.display());
53 }
54 Err(err) => {
55 return Err(err).with_context(|| format!("Failed to inspect path {}", path.display()));
56 }
57 };
58
59 if path.file_name().map(|name| name == "site").unwrap_or(false) {
60 ensure_real_directory(path, &path_metadata, "site directory")?;
61 return Ok(path.to_path_buf());
62 }
63
64 let site_subdir = path.join("site");
65 match std::fs::symlink_metadata(&site_subdir) {
66 Ok(metadata) => {
67 ensure_real_directory(&site_subdir, &metadata, "site directory")?;
68 return Ok(site_subdir);
69 }
70 Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
71 Err(err) => {
72 return Err(err).with_context(|| {
73 format!("Failed to inspect site directory {}", site_subdir.display())
74 });
75 }
76 }
77
78 ensure_real_directory(path, &path_metadata, "site directory")?;
79 Ok(path.to_path_buf())
80}
81
82pub(crate) fn open_existing_sqlite_db(path: &Path) -> Result<Connection> {
83 if !path.exists() {
84 bail!("database does not exist: {}", path.display());
85 }
86
87 frankensqlite::compat::open_with_flags(
90 path.to_string_lossy().as_ref(),
91 OpenFlags::SQLITE_OPEN_READ_ONLY,
92 )
93 .with_context(|| format!("opening sqlite database at {}", path.display()))
94}
95
96#[cfg(not(windows))]
105pub(crate) fn write_file_durably(path: &Path, data: &[u8]) -> Result<()> {
106 let mut f = OpenOptions::new()
107 .create(true)
108 .write(true)
109 .truncate(true)
110 .open(path)
111 .with_context(|| format!("creating {} for durable write", path.display()))?;
112 f.write_all(data)
113 .with_context(|| format!("writing {} durably", path.display()))?;
114 f.sync_all()
115 .with_context(|| format!("fsyncing {} after durable write", path.display()))?;
116 drop(f);
117 let Some(parent) = path.parent().filter(|p| !p.as_os_str().is_empty()) else {
118 return Ok(());
119 };
120 std::fs::File::open(parent)
121 .with_context(|| format!("opening parent {} for fsync", parent.display()))?
122 .sync_all()
123 .with_context(|| {
124 format!(
125 "fsyncing parent {} after durable write to {}",
126 parent.display(),
127 path.display()
128 )
129 })
130}
131
132#[cfg(windows)]
135pub(crate) fn write_file_durably(path: &Path, data: &[u8]) -> Result<()> {
136 std::fs::write(path, data).with_context(|| format!("writing {}", path.display()))
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn write_file_durably_writes_bytes_and_fsyncs() {
145 let tmp = tempfile::tempdir().expect("tempdir");
146 let path = tmp.path().join("out.json");
147 write_file_durably(&path, b"hello").expect("durable write");
148 let got = std::fs::read(&path).expect("read back");
149 assert_eq!(got, b"hello");
150 }
151
152 #[cfg(not(windows))]
153 #[test]
154 fn write_file_durably_surfaces_parent_fsync_error() {
155 let tmp = tempfile::tempdir().expect("tempdir");
160 let nested = tmp.path().join("subdir");
161 std::fs::create_dir(&nested).expect("mkdir");
162 let path = nested.join("out.json");
163
164 std::fs::remove_dir_all(&nested).expect("rm nested");
168 let err = write_file_durably(&path, b"data").unwrap_err();
169 let msg = format!("{err:#}");
170 assert!(
171 msg.contains("creating") || msg.contains("opening parent"),
172 "expected durable write to surface I/O error, got: {msg}"
173 );
174 }
175}