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());
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 let Some(last_component) = stack.current_relative().components().next_back() else {
138 return Ok(());
139 };
140 let last_component = gix_path::try_into_bstr(std::borrow::Cow::Borrowed(last_component.as_os_str().as_ref()))
141 .map_err(|_err| {
142 std::io::Error::other(format!(
143 "Path component {last_component:?} of path \"{}\" contained invalid UTF-8 and could not be validated",
144 stack.current_relative().display()
145 ))
146 })?;
147
148 if let Err(err) = gix_validate::path::component(
149 last_component.as_ref(),
150 mode.and_then(|m| {
151 (m == gix_index::entry::Mode::SYMLINK).then_some(gix_validate::path::component::Mode::Symlink)
152 }),
153 opts,
154 ) {
155 return Err(std::io::Error::other(err));
156 }
157 Ok(())
158}
159
160#[cfg(feature = "attributes")]
161fn create_leading_directory(
162 is_last_component: bool,
163 stack: &gix_fs::Stack,
164 mode: Option<gix_index::entry::Mode>,
165 mkdir_calls: &mut usize,
166 unlink_on_collision: bool,
167 #[cfg_attr(not(windows), allow(unused_variables))] check_terminal_symlinks: bool,
168) -> std::io::Result<()> {
169 if is_last_component && !crate::stack::mode_is_dir(mode).unwrap_or(false) {
170 #[cfg(not(windows))]
171 {
172 return Ok(());
173 }
174 #[cfg(windows)]
175 {
176 if unlink_on_collision || !check_terminal_symlinks {
180 return Ok(());
181 }
182 return match stack.current().symlink_metadata() {
183 Ok(meta) if meta.file_type().is_symlink() => Err(std::io::ErrorKind::AlreadyExists.into()),
184 Ok(_) => Ok(()),
185 Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
186 Err(err) => Err(err),
187 };
188 }
189 }
190 *mkdir_calls += 1;
191 match std::fs::create_dir(stack.current()) {
192 Ok(()) => Ok(()),
193 Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
194 let meta = stack.current().symlink_metadata()?;
195 if meta.is_dir() {
196 Ok(())
197 } else if unlink_on_collision {
198 if meta.file_type().is_symlink() {
199 gix_fs::symlink::remove(stack.current())?;
200 } else {
201 std::fs::remove_file(stack.current())?;
202 }
203 *mkdir_calls += 1;
204 std::fs::create_dir(stack.current())
205 } else {
206 Err(err)
207 }
208 }
209 Err(err) => Err(err),
210 }
211}