1extern crate libc;
3extern crate error_chain;
4
5use std::fmt;
6use std::path::{Path, PathBuf};
7use std::ffi::OsString;
8
9use crate::fd::*;
10use crate::dir::*;
11
12use crate::errors::*;
13
14const MAX_LOOP_CNT: u32 = 256;
15
16struct ChdirLoopEnv {
17 counter: u32,
18 root_stat: Option<libc::stat>,
19}
20
21impl ChdirLoopEnv {
22 fn new() -> ChdirLoopEnv {
23 ChdirLoopEnv {
24 counter: MAX_LOOP_CNT,
25 root_stat: None,
26 }
27 }
28}
29
30impl fmt::Debug for ChdirLoopEnv {
31 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32 write!(f, "counter={:?}, root_stat={:?}",
33 self.counter, self.root_stat.map(|_| "..."))
34 }
35}
36
37struct DirInfo {
38 is_root: bool,
39 stat: libc::stat,
40}
41
42#[derive(Debug)]
81pub struct Chroot {
82 root: PathBuf
83}
84
85impl Chroot {
86 pub fn new<T: AsRef<Path>>(root: &T) -> Self {
87 Chroot {
88 root: root.as_ref().to_path_buf(),
89 }
90 }
91
92 pub fn root_fdraw(&self) -> Result<FdRaw> {
97 let open_flags = libc::O_DIRECTORY | libc::O_CLOEXEC | libc::O_RDONLY;
98
99 FdRaw::open(&self.root, open_flags)
100 }
101
102 pub fn root_fd(&self) -> Result<Fd> {
103 let open_flags = libc::O_DIRECTORY | libc::O_CLOEXEC | libc::O_RDONLY;
104
105 Fd::open(&self.root, open_flags)
106 }
107
108 fn dir_info(&self, dir_fd: &Fd, env: &mut ChdirLoopEnv) -> Result<DirInfo> {
109 if env.root_stat.is_none() {
110 env.root_stat = Some(Fd::cwd().fstatat(&self.root, true)?);
111 }
112
113 let root_stat = env.root_stat.as_ref().unwrap();
114
115 let stat = dir_fd.fstatat(&".", false)?;
116 let is_root =
117 (stat.st_dev == root_stat.st_dev) &&
118 (stat.st_ino == root_stat.st_ino);
119
120 Ok(DirInfo {
121 stat: stat,
122 is_root: is_root,
123 })
124 }
125
126 pub fn chdir<T>(&self, path: &T) -> Result<Fd>
133 where
134 T: AsRef<Path>,
135 {
136 let path : &Path = path.as_ref();
137
138 ensure!(path.is_absolute(), "path '{:?}' not absolute", path);
139
140 let mut env: ChdirLoopEnv = ChdirLoopEnv::new();
141
142 self.chdir_internal(Fd::cwd(), path, &mut env)
143 }
144
145 pub fn chdirat<T>(&self, dir_fd: &Fd, path: &T) -> Result<Fd>
151 where
152 T: AsRef<Path>,
153 {
154 let mut env: ChdirLoopEnv = ChdirLoopEnv::new();
155
156 self.chdir_internal(dir_fd.clone(), path.as_ref(), &mut env)
157 }
158
159 fn open_component(&self, dir_fd: Fd,
160 path: std::path::Component,
161 env: &mut ChdirLoopEnv) -> Result<Fd>
162 {
163 #[allow(clippy::identity_op)]
164 let open_flags = 0
165 | libc::O_DIRECTORY | libc::O_CLOEXEC | libc::O_RDONLY
166 | libc::O_NOFOLLOW;
167
168 match path {
169 std::path::Component::Prefix(_) => {
170 unreachable!();
171 },
172
173 std::path::Component::ParentDir => {
174 let info = self.dir_info(&dir_fd, env)?;
175
176 if info.is_root {
177 Ok(dir_fd)
178 } else {
179 dir_fd.openat(&"..", open_flags)
180 }
181 },
182
183 std::path::Component::RootDir => {
184 self.root_fd()
185 },
186
187 std::path::Component::CurDir => {
188 Ok(dir_fd)
189 },
190
191 std::path::Component::Normal(p) => {
192 dir_fd.openat(&p, open_flags)
193 },
194 }
195 }
196
197 fn chdir_internal(&self, dir_fd: Fd, path: &Path,
198 env: &mut ChdirLoopEnv) -> Result<Fd>
199 {
200 let mut dir_fd = dir_fd;
201
202 for p in path.components() {
203 use std::path::Component;
204
205 dir_fd = match p {
206 Component::Prefix(_) |
207 Component::RootDir |
208 Component::CurDir |
209 Component::ParentDir =>
210 self.open_component(dir_fd, p, env)?,
211
212 Component::Normal(path_name) => {
213 let tmp = Path::new(path_name);
214
215 if !dir_fd.is_lnkat(&tmp) {
216 self.open_component(dir_fd, p, env)?
217 } else if env.counter == 0 {
218 bail!("too much loops while resolving symbolic link '{:?}'",
219 path);
220 } else {
221 let new_path = dir_fd.readlinkat(&tmp)?;
222 let link = Path::new(&new_path);
223
224 env.counter -= 1;
225 let res = self.chdir_internal(dir_fd, link, env);
226 env.counter += 1;
227
228 res?
229 }
230 }
231 };
232 }
233
234 Ok(dir_fd)
235 }
236
237 fn opendir_internal(&self, dir_fd: &Fd, path: &Path, env: &mut ChdirLoopEnv)
238 -> Result<(Fd, OsString)>
239 {
240 let current_dir = OsString::from(".");
241 let fdrc = dir_fd.clone();
242
243 match path.parent() {
244 None =>
245 Ok((self.chdir_internal(fdrc, path, env)?, current_dir)),
246
247 Some(p) => {
248 Ok((
249 self.chdir_internal(fdrc, p, env)?,
250 path.file_name()
251 .unwrap_or_else(|| current_dir.as_os_str())
252 .to_os_string()))
253 }
254 }
255 }
256
257 pub fn openat<T>(&self, dir_fd: &Fd, path: &T, flags: libc::c_int)
263 -> Result<Fd>
264 where
265 T: AsRef<Path>,
266 {
267
268 let mut env = ChdirLoopEnv::new();
269 let mut path = path.as_ref().to_owned();
270 let mut num_loops = MAX_LOOP_CNT;
271
272 while num_loops > 0 {
273 let (dir_fd, comp) =
274 self.opendir_internal(dir_fd, &path, &mut env)?;
275
276 assert_eq!(env.counter, MAX_LOOP_CNT);
277
278 if !dir_fd.is_lnkat(&comp) {
279 return dir_fd.openat(&comp, flags | libc::O_NOFOLLOW);
280 }
281
282 path = Path::new(&dir_fd.readlinkat(&comp)?).to_owned();
283
284 num_loops -= 1;
285 }
286
287 bail!("too much loops while resolving symbolic link '{:?}'",
288 path);
289 }
290
291 pub fn open<T>(&self, path: &T, flags: libc::c_int)
297 -> Result<Fd>
298 where
299 T: AsRef<Path>,
300 {
301 self.openat(&self.root_fd()?, path, flags)
302 }
303
304 pub fn is_lnkat<T>(&self, dir_fd: &Fd, path: &T) -> bool
309 where
310 T: AsRef<Path>,
311 {
312 let mut env = ChdirLoopEnv::new();
313
314 self.opendir_internal(dir_fd, path.as_ref(), &mut env)
315 .map(|(dir_fd, comp)| dir_fd.is_lnkat(&comp))
316 .unwrap_or(false)
317 }
318
319 pub fn is_dirat<T>(&self, dir_fd: &Fd, path: &T) -> bool
324 where
325 T: AsRef<Path>,
326 {
327 let mut env = ChdirLoopEnv::new();
328
329 self.opendir_internal(dir_fd, path.as_ref(), &mut env)
330 .map(|(dir_fd, comp)| dir_fd.is_dirat(&comp))
331 .unwrap_or(false)
332 }
333
334 pub fn is_regat<T>(&self, dir_fd: &Fd, path: &T) -> bool
339 where
340 T: AsRef<Path>,
341 {
342 let mut env = ChdirLoopEnv::new();
343
344 self.opendir_internal(dir_fd, path.as_ref(), &mut env)
345 .map(|(dir_fd, comp)| dir_fd.is_regat(&comp))
346 .unwrap_or(false)
347 }
348
349 pub fn fstatat<T>(&self, dir_fd: &Fd, fname: &T) -> Result<libc::stat>
351 where
352 T: AsRef<Path>,
353 {
354 let do_follow = false;
355
356 let mut env = ChdirLoopEnv::new();
357
358 self.opendir_internal(dir_fd, fname.as_ref(), &mut env)
359 .map(|(dir_fd, comp)| dir_fd.fstatat(&comp, do_follow))?
360 }
361
362 fn check_and_get_entry(dir_fd: &Fd, entry: &DirEntry,
363 info: &DirInfo) -> Result<Option<OsString>> {
364 const DT_UNKNOWN: u8 = 0;
366 const DT_DIR: u8 = libc::DT_DIR;
367
368 if entry.d_ino != info.stat.st_ino {
369 return Ok(None);
370 }
371
372 if entry.d_type != DT_DIR && entry.d_type != DT_UNKNOWN {
373 return Ok(None);
374 }
375
376 let name = OsString::from(entry.name());
377 let stat = dir_fd.fstatat(&name, false)?;
378
379 if ((stat.st_mode & libc::S_IFMT) != libc::S_IFDIR) ||
380 stat.st_ino != info.stat.st_ino ||
381 stat.st_dev != info.stat.st_dev {
382 return Ok(None);
383 }
384
385 Ok(Some(name))
386 }
387
388 pub fn full_path<T>(&self, dir_fd: &Fd, fname: Option<&T>)
395 -> Result<OsString>
396 where
397 T: AsRef<Path>,
398 {
399 let parent_dir = Path::new("..");
400 let mut res = Vec::new();
401 let mut dir_fd = dir_fd.clone();
402 let mut env: ChdirLoopEnv = ChdirLoopEnv::new();
403 let mut total_size = 0;
404
405 loop {
406 let info = self.dir_info(&dir_fd, &mut env)?;
407
408 assert_eq!(env.counter, MAX_LOOP_CNT);
409
410 if info.is_root {
411 break;
412 }
413
414 dir_fd = dir_fd.openat(&parent_dir,
415 libc::O_CLOEXEC | libc::O_RDONLY |
416 libc::O_DIRECTORY)?;
417
418 let dir = Dir::fdopendir(&dir_fd)?;
419
420 for e in ReadDir::new(dir) {
421 let e_name = Self::check_and_get_entry(&dir_fd, &e?, &info)?;
422
423 if let Some(name) = e_name {
424 total_size += name.len() + 1;
425 res.push(name);
426
427 break;
428 }
429 }
430
431 if res.is_empty() {
432 bail!("full_path(): no entry found");
433 }
434 }
435
436 res.reverse();
437 let mut path = OsString::with_capacity(total_size);
438
439 if res.is_empty() && fname.is_none() {
440 path.push("/");
441 }
442
443 for p in res {
444 path.push("/");
445 path.push(p);
446 }
447
448 match fname {
449 None => {}
450 Some(f) => {
451 path.push("/");
452 path.push(f.as_ref().as_os_str());
453 }
454 }
455
456 Ok(path)
457 }
458}
459
460#[cfg(test)]
461#[path="tests/chroot-data.inc.rs"]
462mod testdata;
463
464#[cfg(test)]
465#[path="tests/chroot.inc.rs"]
466mod test;