1use crate::{PathIdMapping, stack::State};
2
3#[derive(Default, Clone, Copy, Debug)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct Statistics {
7 pub num_mkdir_calls: usize,
11 pub push_element: usize,
13 pub push_directory: usize,
15 pub pop_directory: usize,
17}
18
19pub(crate) struct StackDelegate<'a, 'find> {
20 pub state: &'a mut State,
21 pub buf: &'a mut Vec<u8>,
22 #[cfg_attr(not(feature = "attributes"), allow(dead_code))]
23 pub mode: Option<gix_index::entry::Mode>,
24 pub id_mappings: &'a Vec<PathIdMapping>,
25 pub objects: &'find dyn gix_object::Find,
26 pub case: gix_glob::pattern::Case,
27 pub reject_temrinal_symlinks: bool,
28 pub statistics: &'a mut super::Statistics,
29}
30
31impl gix_fs::stack::Delegate for StackDelegate<'_, '_> {
32 fn push_directory(&mut self, stack: &gix_fs::Stack) -> std::io::Result<()> {
33 self.statistics.delegate.push_directory += 1;
34 let rela_dir_bstr = gix_path::into_bstr(stack.current_relative()).map_err(std::io::Error::other)?;
35 let rela_dir = gix_path::to_unix_separators_on_windows(rela_dir_bstr);
36 match &mut self.state {
37 #[cfg(feature = "attributes")]
38 State::CreateDirectoryAndAttributesStack { attributes, .. } | State::AttributesStack(attributes) => {
39 attributes.push_directory(
40 stack.root(),
41 stack.current(),
42 &rela_dir,
43 self.buf,
44 self.id_mappings,
45 self.objects,
46 &mut self.statistics.attributes,
47 )?;
48 }
49 #[cfg(feature = "attributes")]
50 State::AttributesAndIgnoreStack { ignore, attributes } => {
51 attributes.push_directory(
52 stack.root(),
53 stack.current(),
54 &rela_dir,
55 self.buf,
56 self.id_mappings,
57 self.objects,
58 &mut self.statistics.attributes,
59 )?;
60 ignore.push_directory(
61 stack.root(),
62 stack.current(),
63 &rela_dir,
64 self.buf,
65 self.id_mappings,
66 self.objects,
67 self.case,
68 &mut self.statistics.ignore,
69 )?;
70 }
71 State::IgnoreStack(ignore) => ignore.push_directory(
72 stack.root(),
73 stack.current(),
74 &rela_dir,
75 self.buf,
76 self.id_mappings,
77 self.objects,
78 self.case,
79 &mut self.statistics.ignore,
80 )?,
81 }
82 Ok(())
83 }
84
85 #[cfg_attr(not(feature = "attributes"), allow(unused_variables))]
86 fn push(&mut self, is_last_component: bool, stack: &gix_fs::Stack) -> std::io::Result<()> {
87 self.statistics.delegate.push_element += 1;
88 match &mut self.state {
89 #[cfg(feature = "attributes")]
90 State::CreateDirectoryAndAttributesStack {
91 unlink_on_collision,
92 validate,
93 attributes: _,
94 } => {
95 validate_last_component(stack, self.mode, *validate)?;
96 create_leading_directory(
97 is_last_component,
98 stack,
99 self.mode,
100 &mut self.statistics.delegate.num_mkdir_calls,
101 *unlink_on_collision,
102 self.reject_temrinal_symlinks,
103 )?;
104 }
105 #[cfg(feature = "attributes")]
106 State::AttributesAndIgnoreStack { .. } | State::AttributesStack(_) => {}
107 State::IgnoreStack(_) => {}
108 }
109 Ok(())
110 }
111
112 fn pop_directory(&mut self) {
113 self.statistics.delegate.pop_directory += 1;
114 match &mut self.state {
115 #[cfg(feature = "attributes")]
116 State::CreateDirectoryAndAttributesStack { attributes, .. } | State::AttributesStack(attributes) => {
117 attributes.pop_directory();
118 }
119 #[cfg(feature = "attributes")]
120 State::AttributesAndIgnoreStack { attributes, ignore } => {
121 attributes.pop_directory();
122 ignore.pop_directory();
123 }
124 State::IgnoreStack(ignore) => {
125 ignore.pop_directory();
126 }
127 }
128 }
129}
130
131#[cfg(feature = "attributes")]
132fn validate_last_component(
133 stack: &gix_fs::Stack,
134 mode: Option<gix_index::entry::Mode>,
135 opts: gix_validate::path::component::Options,
136) -> std::io::Result<()> {
137 use gix_error::ResultExt;
138
139 let Some(last_component) = stack.current_relative().components().next_back() else {
140 return Ok(());
141 };
142 let last_component = gix_path::into_bstr(std::borrow::Cow::Borrowed(last_component.as_os_str().as_ref()))
143 .or_raise(|| {
144 gix_error::message!(
145 "Path component {last_component:?} of path \"{}\" contained invalid UTF-8 and could not be validated",
146 stack.current_relative().display()
147 )
148 })
149 .map_err(std::io::Error::other)?;
150
151 if let Err(err) = gix_validate::path::component(
152 last_component.as_ref(),
153 mode.and_then(|m| {
154 (m == gix_index::entry::Mode::SYMLINK).then_some(gix_validate::path::component::Mode::Symlink)
155 }),
156 opts,
157 ) {
158 return Err(std::io::Error::other(err));
159 }
160 Ok(())
161}
162
163#[cfg(feature = "attributes")]
164fn create_leading_directory(
165 is_last_component: bool,
166 stack: &gix_fs::Stack,
167 mode: Option<gix_index::entry::Mode>,
168 mkdir_calls: &mut usize,
169 unlink_on_collision: bool,
170 #[cfg_attr(not(windows), allow(unused_variables))] check_terminal_symlinks: bool,
171) -> std::io::Result<()> {
172 if is_last_component && !crate::stack::mode_is_dir(mode).unwrap_or(false) {
173 #[cfg(not(windows))]
174 {
175 return Ok(());
176 }
177 #[cfg(windows)]
178 {
179 if unlink_on_collision || !check_terminal_symlinks {
183 return Ok(());
184 }
185 return match stack.current().symlink_metadata() {
186 Ok(meta) if meta.file_type().is_symlink() => Err(std::io::ErrorKind::AlreadyExists.into()),
187 Ok(_) => Ok(()),
188 Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
189 Err(err) => Err(err),
190 };
191 }
192 }
193 *mkdir_calls += 1;
194 match std::fs::create_dir(stack.current()) {
195 Ok(()) => Ok(()),
196 Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
197 let meta = stack.current().symlink_metadata()?;
198 if meta.is_dir() {
199 Ok(())
200 } else if unlink_on_collision {
201 if meta.file_type().is_symlink() {
202 gix_fs::symlink::remove(stack.current())?;
203 } else {
204 std::fs::remove_file(stack.current())?;
205 }
206 *mkdir_calls += 1;
207 std::fs::create_dir(stack.current())
208 } else {
209 Err(err)
210 }
211 }
212 Err(err) => Err(err),
213 }
214}