diskplan_filesystem/
physical.rs1use std::{borrow::Cow, fs, io::Write, os::unix::fs::PermissionsExt};
2
3use anyhow::{anyhow, Context, Result};
4use camino::{Utf8Path, Utf8PathBuf};
5use nix::{
6 sys::stat,
7 unistd::{Gid, Uid},
8};
9use users::{Groups, Users, UsersCache};
10
11use super::{
12 attributes::Mode, Attrs, Filesystem, SetAttrs, DEFAULT_DIRECTORY_MODE, DEFAULT_FILE_MODE,
13};
14
15#[derive(Default)]
17pub struct DiskFilesystem {
18 users: UsersCache,
19}
20
21impl Filesystem for DiskFilesystem {
22 fn create_directory(&mut self, path: impl AsRef<Utf8Path>, attrs: SetAttrs) -> Result<()> {
23 fs::create_dir(path.as_ref())?;
24 self.apply_attrs(path, attrs, DEFAULT_DIRECTORY_MODE)
25 }
26
27 fn create_file(
28 &mut self,
29 path: impl AsRef<Utf8Path>,
30 attrs: SetAttrs,
31 content: String,
32 ) -> Result<()> {
33 let mut file = fs::File::create(path.as_ref())?;
34 file.write_all(content.as_bytes())?;
35 self.apply_attrs(path, attrs, DEFAULT_FILE_MODE)
36 }
37
38 fn create_symlink(
39 &mut self,
40 path: impl AsRef<Utf8Path>,
41 target: impl AsRef<Utf8Path>,
42 ) -> Result<()> {
43 Ok(std::os::unix::fs::symlink(target.as_ref(), path.as_ref())?)
44 }
45
46 fn exists(&self, path: impl AsRef<Utf8Path>) -> bool {
47 fs::metadata(path.as_ref()).is_ok()
48 }
49
50 fn is_directory(&self, path: impl AsRef<Utf8Path>) -> bool {
51 fs::metadata(path.as_ref())
52 .map(|m| m.file_type().is_dir())
53 .unwrap_or(false)
54 }
55
56 fn is_file(&self, path: impl AsRef<Utf8Path>) -> bool {
57 fs::metadata(path.as_ref())
58 .map(|m| m.file_type().is_file())
59 .unwrap_or(false)
60 }
61
62 fn is_link(&self, path: impl AsRef<Utf8Path>) -> bool {
63 fs::symlink_metadata(path.as_ref())
64 .map(|m| m.file_type().is_symlink())
65 .unwrap_or(false)
66 }
67
68 fn list_directory(&self, path: impl AsRef<Utf8Path>) -> Result<Vec<String>> {
69 let mut listing = Vec::new();
70 for entry in fs::read_dir(path.as_ref())? {
71 let entry = entry?;
72 let file_name = entry.file_name();
73 listing.push(file_name.to_string_lossy().into_owned());
74 }
75 Ok(listing)
76 }
77
78 fn read_file(&self, path: impl AsRef<Utf8Path>) -> Result<String> {
79 fs::read_to_string(path.as_ref()).map_err(Into::into)
80 }
81
82 fn read_link(&self, path: impl AsRef<Utf8Path>) -> Result<Utf8PathBuf> {
83 Ok(fs::read_link(path.as_ref())?.try_into()?)
84 }
85
86 fn attributes(&self, path: impl AsRef<Utf8Path>) -> Result<Attrs> {
87 let stat = stat::stat(path.as_ref().as_std_path())?;
88 let owner = Cow::Owned(
89 self.users
90 .get_user_by_uid(stat.st_uid)
91 .ok_or_else(|| anyhow!("Failed to get user from UID: {}", stat.st_uid))?
92 .name()
93 .to_string_lossy()
94 .into_owned(),
95 );
96 let group = Cow::Owned(
97 self.users
98 .get_group_by_gid(stat.st_gid)
99 .ok_or_else(|| anyhow!("Failed to get group from GID: {}", stat.st_gid))?
100 .name()
101 .to_string_lossy()
102 .into_owned(),
103 );
104 let mode = (stat.st_mode as u16).into();
105 Ok(Attrs { owner, group, mode })
106 }
107
108 fn set_attributes(&mut self, path: impl AsRef<Utf8Path>, attrs: SetAttrs) -> Result<()> {
109 let path = path.as_ref();
110 self.apply_attrs(
111 path,
112 attrs,
113 if self.is_directory(path) {
114 DEFAULT_DIRECTORY_MODE
115 } else {
116 DEFAULT_FILE_MODE
117 },
118 )
119 }
120}
121
122impl DiskFilesystem {
123 pub fn new() -> Self {
125 DiskFilesystem {
126 users: UsersCache::new(),
127 }
128 }
129
130 fn apply_attrs(
131 &self,
132 path: impl AsRef<Utf8Path>,
133 attrs: SetAttrs,
134 default_mode: Mode,
135 ) -> Result<()> {
136 let uid = match attrs.owner {
137 Some(owner) => Some(Uid::from_raw(
138 self.users
139 .get_user_by_name(owner)
140 .ok_or_else(|| anyhow!("No such user: {}", owner))?
141 .uid(),
142 )),
143 None => None,
144 };
145 let gid = match attrs.group {
146 Some(group) => Some(Gid::from_raw(
147 self.users
148 .get_group_by_name(group)
149 .ok_or_else(|| anyhow!("No such group: {}", group))?
150 .gid(),
151 )),
152 None => None,
153 };
154 let mode = PermissionsExt::from_mode(attrs.mode.unwrap_or(default_mode).into());
155
156 tracing::trace!("chown {:?} {:?}:{:?}", path.as_ref(), uid, gid);
157 nix::unistd::chown(path.as_ref().as_std_path(), uid, gid)
158 .with_context(|| format!("Changing ownership of {:?}", path.as_ref()))?;
159 fs::set_permissions(path.as_ref(), mode)?;
160 Ok(())
161 }
162}