1use anyhow::{Context, Result};
22use std::fs::{File, OpenOptions};
23#[cfg(unix)]
24use std::os::fd::{AsRawFd, RawFd};
25use std::path::{Path, PathBuf};
26
27use crate::group::{GroupKey, GroupLayout};
28
29pub struct Layout {
30 pub dir: PathBuf,
31 pub spec: GroupLayout,
32 files: Vec<File>,
36}
37
38impl Layout {
39 pub fn create(dir: &Path, spec: GroupLayout) -> Result<Self> {
40 std::fs::create_dir_all(dir).with_context(|| format!("mkdir {}", dir.display()))?;
41 let mut files = Vec::with_capacity(spec.num_layers as usize);
42 for layer in 0..spec.num_layers {
43 let p = dir.join(format!("layer_{layer:05}.kv"));
44 let mut opts = OpenOptions::new();
45 opts.read(true).write(true).create(true).truncate(false);
46 set_direct_flag(&mut opts);
47 let f = opts
48 .open(&p)
49 .with_context(|| format!("open {}", p.display()))?;
50 preallocate(&f, spec.bytes_per_layer())
51 .with_context(|| format!("preallocate {}", p.display()))?;
52 files.push(f);
53 }
54 Ok(Self {
55 dir: dir.to_path_buf(),
56 spec,
57 files,
58 })
59 }
60
61 pub fn open(dir: &Path, spec: GroupLayout) -> Result<Self> {
63 let mut files = Vec::with_capacity(spec.num_layers as usize);
64 for layer in 0..spec.num_layers {
65 let p = dir.join(format!("layer_{layer:05}.kv"));
66 let mut opts = OpenOptions::new();
67 opts.read(true).write(true);
68 set_direct_flag(&mut opts);
69 let f = opts
70 .open(&p)
71 .with_context(|| format!("open {}", p.display()))?;
72 let len = f.metadata()?.len();
73 if len < spec.bytes_per_layer() {
74 anyhow::bail!(
75 "layer file {} is undersized: {} < {}",
76 p.display(),
77 len,
78 spec.bytes_per_layer()
79 );
80 }
81 files.push(f);
82 }
83 Ok(Self {
84 dir: dir.to_path_buf(),
85 spec,
86 files,
87 })
88 }
89
90 #[cfg(unix)]
92 pub fn fd(&self, layer: u32) -> RawFd {
93 self.files[layer as usize].as_raw_fd()
94 }
95
96 pub fn file(&self, layer: u32) -> &File {
99 &self.files[layer as usize]
100 }
101
102 pub fn offset(&self, key: GroupKey) -> u64 {
103 self.spec.file_offset(key)
104 }
105
106 pub fn group_bytes(&self) -> u64 {
107 self.spec.group_bytes()
108 }
109}
110
111#[cfg(target_os = "linux")]
113fn set_direct_flag(opts: &mut OpenOptions) {
114 use std::os::unix::fs::OpenOptionsExt;
115 opts.custom_flags(libc::O_DIRECT);
116}
117
118#[cfg(not(target_os = "linux"))]
119fn set_direct_flag(_opts: &mut OpenOptions) {}
120
121#[cfg(unix)]
122fn preallocate(file: &File, size: u64) -> Result<()> {
123 let fd = file.as_raw_fd();
127 let res = unsafe { libc::posix_fallocate(fd, 0, size as libc::off_t) };
128 if res != 0 {
129 anyhow::bail!("posix_fallocate({size}) failed: {res}");
130 }
131 Ok(())
132}
133
134#[cfg(windows)]
139fn preallocate(file: &File, size: u64) -> Result<()> {
140 file.set_len(size)
141 .with_context(|| format!("set_len({size})"))?;
142 Ok(())
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::group::{GroupKey, KvKind};
149
150 #[test]
151 fn create_open_round_trip() {
152 let tmp = tempdir();
153 let spec = GroupLayout::new(2, 4, 2, 16, 128, 2, 4096);
154 {
155 let l = Layout::create(&tmp, spec).unwrap();
156 assert_eq!(l.spec.num_layers, 2);
157 let p = tmp.join("layer_00000.kv");
159 let len = std::fs::metadata(&p).unwrap().len();
160 assert_eq!(len, spec.bytes_per_layer());
161 }
162 {
163 let l = Layout::open(&tmp, spec).unwrap();
164 let off = l.offset(GroupKey::new(0, 1, 1, KvKind::V));
165 assert_eq!(off, spec.file_offset(GroupKey::new(0, 1, 1, KvKind::V)));
166 }
167 std::fs::remove_dir_all(&tmp).ok();
168 }
169
170 fn tempdir() -> PathBuf {
171 let p = std::env::temp_dir().join(format!("atlas-storage-test-{}", std::process::id()));
172 std::fs::create_dir_all(&p).unwrap();
173 p
174 }
175}