iceoryx2_bb_posix/
directory.rs

1// Copyright (c) 2023 Contributors to the Eclipse Foundation
2//
3// See the NOTICE file(s) distributed with this work for additional
4// information regarding copyright ownership.
5//
6// This program and the accompanying materials are made available under the
7// terms of the Apache Software License 2.0 which is available at
8// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9// which is available at https://opensource.org/licenses/MIT.
10//
11// SPDX-License-Identifier: Apache-2.0 OR MIT
12
13//! Create and read directory contents based on a POSIX api. It provides also advanced features
14//! like [`Permission`] setting and to be created from a [`FileDescriptor`]
15//!
16//! # Examples
17//! ```
18//! # extern crate iceoryx2_loggers;
19//!
20//! use iceoryx2_bb_posix::directory::*;
21//! use iceoryx2_bb_system_types::path::Path;
22//! use iceoryx2_bb_container::semantic_string::SemanticString;
23//!
24//! let dir_name = Path::new(b"a_dir_over_the_rainbow").unwrap();
25//! let dir = if !Directory::does_exist(&dir_name).unwrap() {
26//!   Directory::create(&dir_name, Permission::OWNER_ALL).unwrap()
27//! } else {
28//!   Directory::new(&dir_name).unwrap()
29//! };
30//!
31//! let contents = dir.contents().unwrap();
32//! for entry in contents {
33//!   println!("{}", entry.name());
34//! }
35//!
36//! Directory::remove(&dir_name).unwrap();
37//! ```
38
39use alloc::format;
40use alloc::vec;
41use alloc::vec::Vec;
42
43use iceoryx2_bb_container::semantic_string::SemanticString;
44use iceoryx2_bb_container::string::strnlen;
45use iceoryx2_bb_elementary::enum_gen;
46use iceoryx2_bb_elementary::scope_guard::ScopeGuardBuilder;
47use iceoryx2_bb_system_types::{file_name::FileName, file_path::FilePath, path::Path};
48use iceoryx2_log::{error, fail, fatal_panic, trace};
49use iceoryx2_pal_configuration::PATH_SEPARATOR;
50use iceoryx2_pal_posix::posix::MemZeroedStruct;
51use iceoryx2_pal_posix::*;
52use iceoryx2_pal_posix::{posix::errno::Errno, posix::S_IFDIR};
53
54use crate::file::{File, FileRemoveError};
55use crate::file_type::FileType;
56pub use crate::permission::Permission;
57use crate::{config::EINTR_REPETITIONS, file_descriptor::*, metadata::*};
58
59enum_gen! { DirectoryOpenError
60  entry:
61    LoopInSymbolicLinks,
62    InsufficientPermissions,
63    NotADirectory,
64    PerProcessFileHandleLimitReached,
65    SystemWideFileHandleLimitReached,
66    DoesNotExist,
67    UnknownError(i32)
68}
69
70#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
71pub enum DirectoryStatError {
72    InsufficientPermissions,
73    IOerror,
74    DoesNotExist,
75    PathPrefixIsNotADirectory,
76    DataOverflowInStatStruct,
77    LoopInSymbolicLinks,
78    UnknownError(i32),
79}
80
81enum_gen! { DirectoryReadError
82  entry:
83    InsufficientPermissions,
84    DirectoryDoesNoLongerExist,
85    InsufficientMemory,
86    PerProcessFileHandleLimitReached,
87    SystemWideFileHandleLimitReached,
88    UnknownError(i32)
89
90  mapping:
91    DirectoryStatError
92}
93
94enum_gen! { DirectoryCreateError
95  entry:
96    InsufficientPermissions,
97    DirectoryAlreadyExists,
98    LoopInSymbolicLinks,
99    ExceedsParentsLinkCount,
100    PartsOfThePathDoNotExist,
101    PartsOfThePathAreNotADirectory,
102    NoSpaceLeft,
103    ReadOnlyFilesystem,
104    UnableToApplyPermissions,
105    UnknownError(i32)
106  mapping:
107    DirectoryOpenError
108}
109
110enum_gen! { DirectoryRemoveError
111  entry:
112    InsufficientPermissions,
113    CurrentlyInUse,
114    NotEmptyOrHardLinksPointingToTheDirectory,
115    IOerror,
116    LastComponentIsDot,
117    LoopInSymbolicLinks,
118    DirectoryDoesNotExist,
119    NotADirectory,
120    ResidesOnReadOnlyFileSystem,
121    DanglingSymbolicLink,
122    UnknownError(i32)
123  mapping:
124    DirectoryOpenError,
125    DirectoryReadError,
126    FileRemoveError
127}
128
129#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
130pub enum DirectoryAccessError {
131    InsufficientPermissions,
132    IOerror,
133    PathPrefixIsNotADirectory,
134    DataOverflowInStatStruct,
135    LoopInSymbolicLinks,
136    UnknownError(i32),
137}
138
139enum_gen! {
140    /// The DirectoryError enum is a generalization when one doesn't require the fine-grained error
141    /// handling enums. One can forward DirectoryError as more generic return value when a method
142    /// returns a Directory***Error.
143    /// On a higher level it is again convertable to [`crate::Error`].
144    DirectoryError
145  generalization:
146    Create <= DirectoryCreateError,
147    Open <= DirectoryOpenError,
148    Read <= DirectoryReadError; DirectoryStatError,
149    Remove <= DirectoryRemoveError
150}
151
152/// Represents an entry of a [`Directory`]. It provides the name of the entry and [`Metadata`] to
153/// acquire additional informations about the entry.
154///
155/// # Example
156///
157/// ```
158/// # extern crate iceoryx2_loggers;
159///
160/// use iceoryx2_bb_posix::directory::*;
161/// use iceoryx2_bb_system_types::path::Path;
162/// use iceoryx2_bb_container::semantic_string::SemanticString;
163///
164/// let dir_name = Path::new(b"i_am_a_directory").unwrap();
165/// let dir = Directory::create(&dir_name, Permission::OWNER_ALL).unwrap();
166/// let content = dir.contents().unwrap();
167///
168/// for entry in content {
169///   println!("name {}, type {:?}, size {}", entry.name(), entry.metadata().file_type(),
170///     entry.metadata().size());
171/// }
172/// Directory::remove(&dir_name).unwrap();
173/// ```
174pub struct DirectoryEntry {
175    name: FileName,
176    metadata: Metadata,
177}
178
179impl DirectoryEntry {
180    pub fn name(&self) -> &FileName {
181        &self.name
182    }
183
184    pub fn metadata(&self) -> &Metadata {
185        &self.metadata
186    }
187}
188
189/// Represents a directory implement based on the POSIX API. It implements the traits
190/// [`FileDescriptorBased`] and [`FileDescriptorManagement`] to provide extended permission
191/// and ownership handling as well as [`Metadata`].
192#[derive(Debug)]
193pub struct Directory {
194    path: Path,
195    directory_stream: *mut posix::DIR,
196    file_descriptor: FileDescriptor,
197}
198
199impl Drop for Directory {
200    fn drop(&mut self) {
201        let mut counter = 0;
202        loop {
203            if unsafe { posix::closedir(self.directory_stream) } == 0 {
204                break;
205            }
206
207            let msg = "Unable to close directory stream";
208            match Errno::get() {
209                Errno::EBADF => {
210                    fatal_panic!(from self, "This should never happen! {} due to an invalid file-descriptor.", msg);
211                }
212                Errno::EINTR => {
213                    counter += 1;
214                    if counter > EINTR_REPETITIONS {
215                        error!(from self, "{} since too many interrupt signals were received.", msg);
216                    }
217                }
218                v => {
219                    fatal_panic!(from self, "This should never happen! {} since an unknown error occurred ({}).", msg, v);
220                }
221            }
222
223            if counter > EINTR_REPETITIONS {
224                error!(from self, "Tried {} times to close the file but failed.", counter);
225            }
226        }
227    }
228}
229
230impl Directory {
231    pub fn new(path: &Path) -> Result<Self, DirectoryOpenError> {
232        let directory_stream = unsafe { posix::opendir(path.as_c_str()) };
233
234        let msg = format!("Unable to open directory \"{path}\"");
235        if directory_stream.is_null() {
236            handle_errno!(DirectoryOpenError, from "Directory::new",
237                Errno::EACCES => (InsufficientPermissions, "{} due to insufficient permissions.", msg),
238                Errno::ELOOP => (LoopInSymbolicLinks, "{} due to a loop in the symbolic links.", msg),
239                Errno::ENOENT => (DoesNotExist, "{} since the path does not exist.", msg),
240                Errno::ENOTDIR => (NotADirectory, "{} since the path is not a directory.", msg),
241                Errno::EMFILE => (PerProcessFileHandleLimitReached, "{} since the file descriptor limit of the process was reached.", msg),
242                Errno::ENFILE => (SystemWideFileHandleLimitReached, "{} since the system-wide limit of file descriptors was reached.", msg),
243                v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
244            );
245        }
246
247        let file_descriptor =
248            FileDescriptor::non_owning_new(unsafe { posix::dirfd(directory_stream) });
249        if file_descriptor.is_none() {
250            fatal_panic!(from "Directory::new",
251                "This should never happen! {} since 'dirfd' states that the acquired directory stream is invalid.", msg);
252        }
253
254        Ok(Directory {
255            path: *path,
256            directory_stream,
257            file_descriptor: file_descriptor.unwrap(),
258        })
259    }
260
261    fn create_single_directory(
262        path: &Path,
263        permission: Permission,
264    ) -> Result<(), DirectoryCreateError> {
265        let origin = "Directory::create()";
266        let msg = format!("Unable to create directory \"{path}\"");
267
268        if unsafe { posix::mkdir(path.as_c_str(), permission.as_mode()) } == -1 {
269            handle_errno!(DirectoryCreateError, from origin,
270                Errno::EACCES => (InsufficientPermissions, "{} due to insufficient permissions.", msg),
271                Errno::EEXIST => (DirectoryAlreadyExists, "{} since the directory already exists.", msg),
272                Errno::ELOOP => (LoopInSymbolicLinks, "{} due to a loop in the symbolic links.", msg),
273                Errno::EMLINK => (ExceedsParentsLinkCount, "{} since it would exceed the parents link count.", msg),
274                Errno::ENOENT => (PartsOfThePathDoNotExist, "{} since parts of the path either do not exist.", msg),
275                Errno::ENOSPC => (NoSpaceLeft, "{} since there is no space left on the target device.", msg),
276                Errno::ENOTDIR => (PartsOfThePathAreNotADirectory, "{} since parts of the path are not a directory.", msg),
277                Errno::EROFS => (ReadOnlyFilesystem, "{} since the parent directory resides on a read-only filesystem.", msg),
278                v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
279            );
280        }
281
282        // Reapply the permissions since the `umask` call can influence the
283        // permission creation. It would be possible to adjust the umask(0)
284        // and restore it afterwards but this applies the umask to the whole
285        // process and could influence a thread which calls umask as well.
286        //
287        // So since umask is out, we have to reapply the permissions again,
288        // after the directory was created.
289        let mut dir = match Directory::new(path) {
290            Ok(dir) => dir,
291            Err(e) => {
292                let _ = Directory::remove(path);
293                fail!(from origin, with DirectoryCreateError::UnableToApplyPermissions,
294                    "{msg} since it could not be opened after creation to apply the permissions due to {e:?}.");
295            }
296        };
297
298        match dir.set_permission(permission) {
299            Ok(()) => Ok(()),
300            Err(e) => {
301                let _ = Directory::remove(path);
302                fail!(from origin, with DirectoryCreateError::UnableToApplyPermissions,
303                    "{msg} since the permissions could not be applied due to {e:?}.");
304            }
305        }
306    }
307
308    /// Creates a new directory at the provided path.
309    pub fn create(path: &Path, permission: Permission) -> Result<Self, DirectoryCreateError> {
310        let origin = "Directory::create()";
311        let msg = format!("Unable to create directory \"{path}\"");
312        let entries = path.entries();
313
314        let mut inc_path = if path.is_absolute() {
315            Path::new_root_path()
316        } else {
317            Path::new_empty()
318        };
319
320        for entry in entries {
321            inc_path
322                .add_path_entry(&entry.into())
323                .expect("Always works since it recreates the provided path");
324
325            match Directory::does_exist(&inc_path) {
326                Ok(true) => (),
327                Ok(false) => match Directory::create_single_directory(&inc_path, permission) {
328                    Ok(()) | Err(DirectoryCreateError::DirectoryAlreadyExists) => (),
329                    Err(e) => {
330                        fail!(from origin, with e,
331                            "{} since the directory {} could not be created due to {:?}.",
332                            msg, inc_path, e);
333                    }
334                },
335                Err(DirectoryAccessError::InsufficientPermissions) => {
336                    fail!(from origin, with DirectoryCreateError::InsufficientPermissions,
337                        "{} since the path {} could not be accessed due to insufficient permissions.", msg, inc_path);
338                }
339                Err(DirectoryAccessError::PathPrefixIsNotADirectory) => {
340                    fail!(from origin, with DirectoryCreateError::PartsOfThePathAreNotADirectory,
341                        "{} since the path {} is not a directory.", msg, inc_path);
342                }
343                Err(v) => {
344                    fail!(from origin, with DirectoryCreateError::UnknownError(0),
345                        "{} due to a failure while accessing {} ({:?}).", msg, inc_path, v);
346                }
347            };
348        }
349
350        match Directory::new(path) {
351            Ok(d) => {
352                trace!(from d, "created with permissions \"{permission}\"");
353                Ok(d)
354            }
355            Err(e) => {
356                fail!(from origin, with e.into(),
357                    "Failed to open newly created directory \"{}\".", path);
358            }
359        }
360    }
361
362    /// Returns the path of the directory.
363    pub fn path(&self) -> &Path {
364        &self.path
365    }
366
367    /// Removes an empty directory. If the directory is not empty it returns an error.
368    pub fn remove_empty(path: &Path) -> Result<(), DirectoryRemoveError> {
369        if unsafe { posix::rmdir(path.as_c_str()) } == -1 {
370            let msg = format!("Unable to remove empty directory \"{path}\"");
371            handle_errno!(DirectoryRemoveError, from "Directory::remove",
372                Errno::EACCES => (InsufficientPermissions, "{} due to insufficient permissions.", msg),
373                Errno::EPERM => (InsufficientPermissions, "{} due to insufficient permissions.", msg),
374                Errno::EBUSY => (CurrentlyInUse, "{} since the directory is currently in use.", msg),
375                Errno::EINVAL => (LastComponentIsDot, "{} since the last path component is \".\".", msg),
376                Errno::ELOOP => (LoopInSymbolicLinks, "{} due to a loop in the symbolic links of the path \".\".", msg),
377                Errno::ENOENT => (DanglingSymbolicLink, "{} since the path contains a dangling symbolic link.", msg),
378                Errno::EEXIST => (DirectoryDoesNotExist, "{} since the directory does not exist.", msg),
379                Errno::ENOTDIR => (NotADirectory, "{} since it is not a directory.", msg),
380                Errno::EROFS => (ResidesOnReadOnlyFileSystem, "{} since the directory resides on a read only file system.", msg),
381                Errno::ENOTEMPTY => (NotEmptyOrHardLinksPointingToTheDirectory, "{} since the directory is not empty or there are hard links pointing to the directory.", msg),
382                Errno::EIO => (IOerror, "{} due to a physicial I/O error.", msg),
383                v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
384            );
385        }
386
387        trace!(from "Directory::remove", "removed \"{}\"", path);
388        Ok(())
389    }
390
391    /// Removes and existing directory with all of its contents.
392    pub fn remove(path: &Path) -> Result<(), DirectoryRemoveError> {
393        let msg = format!("Unable to remove directory \"{path}\"");
394        let origin = "Directory::remove()";
395
396        let dir = fail!(from origin, when Directory::new(path),
397                            "{} since the directory {} could not be opened.", msg, path);
398        let contents = fail!(from origin, when dir.contents(),
399                            "{} since the directory contents of {} could not be read.", msg, path);
400
401        for entry in contents {
402            let mut sub_path = *path;
403            sub_path
404                .add_path_entry(&entry.name().into())
405                .expect("always a valid path entry");
406            if entry.metadata().file_type() == FileType::Directory {
407                fail!(from origin, when Directory::remove(&sub_path),
408                    "{} since the sub-path {} could not be removed.", msg, sub_path);
409            } else {
410                fail!(from origin, when File::remove(&unsafe{FilePath::new_unchecked(sub_path.as_bytes())}),
411                    "{} since the file {} could not be removed.", msg, sub_path);
412            }
413        }
414
415        Self::remove_empty(path)
416    }
417
418    /// Returns the contents of the directory inside a vector of [`DirectoryEntry`]s.
419    pub fn contents(&self) -> Result<Vec<DirectoryEntry>, DirectoryReadError> {
420        let mut namelist: *mut *mut posix::types::dirent =
421            core::ptr::null_mut::<*mut posix::types::dirent>();
422        let number_of_directory_entries =
423            unsafe { posix::scandir(self.path.as_c_str(), &mut namelist) };
424
425        let _memory_cleanup_guard = ScopeGuardBuilder::new(namelist)
426            .on_init(|_| {
427                if number_of_directory_entries < 0 {
428                    let msg = "Unable to read directory contents";
429                    handle_errno!(DirectoryReadError, from self,
430                        Errno::EACCES => (InsufficientPermissions, "{} due to insufficient permissions.", msg),
431                        Errno::ENOENT => (DirectoryDoesNoLongerExist, "{} since the directory does not exist anymore.", msg),
432                        Errno::ENOMEM => (InsufficientMemory, "{} due to insufficient memory.", msg),
433                        Errno::EMFILE => (PerProcessFileHandleLimitReached, "{} since the file descriptor limit of the process was reached.", msg),
434                        Errno::ENFILE => (SystemWideFileHandleLimitReached, "{} since the system-wide limit of file descriptors was reached.", msg),
435                        v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
436                    );
437                }
438
439                Ok(())
440            })
441            .on_drop(|v| {
442                for i in 0..number_of_directory_entries {
443                    unsafe { posix::free(*(v.offset(i as isize)) as *mut posix::void) };
444                }
445                unsafe { posix::free(*v as *mut posix::void) };
446            }).create()?;
447
448        let mut contents: Vec<DirectoryEntry> = vec![];
449        for i in 0..number_of_directory_entries {
450            let raw_name =
451                unsafe { (*(*namelist.offset(i as isize))).d_name.as_ptr() as *mut posix::c_char };
452            let raw_name_length = unsafe { strnlen(raw_name, FileName::max_len()) };
453
454            if raw_name_length == 0 {
455                continue;
456            }
457
458            const DOT: posix::c_char = b'.' as _;
459            // dot is skipped
460            if raw_name_length == 1 && unsafe { *raw_name == DOT } {
461                continue;
462            }
463
464            // dot dot is skipped
465            if raw_name_length == 2
466                && unsafe { *raw_name == DOT }
467                && unsafe { *raw_name.offset(1) == DOT }
468            {
469                continue;
470            }
471
472            match unsafe { FileName::from_c_str(raw_name) } {
473                Ok(name) => {
474                    let msg = format!(
475                        "Failed to acquire stats \"{name}\" while reading directory content"
476                    );
477                    match Self::acquire_metadata(self, &name, &msg) {
478                        Ok(metadata) => contents.push(DirectoryEntry { name, metadata }),
479                        Err(DirectoryStatError::DoesNotExist)
480                        | Err(DirectoryStatError::InsufficientPermissions) => (),
481                        Err(e) => {
482                            fail!(from self, with e.into(),
483                                    "{} due to an internal failure {:?}.", msg, e);
484                        }
485                    }
486                }
487                Err(v) => {
488                    error!(from self, "Directory contains entries that are not representable with FileName struct ({:?}).", v);
489                }
490            }
491        }
492
493        Ok(contents)
494    }
495
496    /// Returns true if a directory already exists, otherwise false
497    pub fn does_exist(path: &Path) -> Result<bool, DirectoryAccessError> {
498        let mut buffer = posix::stat_t::new_zeroed();
499        let msg = format!("Unable to determine if \"{path}\" does exist");
500
501        if unsafe { posix::stat(path.as_c_str(), &mut buffer) } == -1 {
502            handle_errno!(DirectoryAccessError, from "Directory::does_exist",
503                success Errno::ENOENT => false,
504                Errno::EACCES => (InsufficientPermissions, "{} due to insufficient permissions to open path.", msg),
505                Errno::EIO => (IOerror, "{} due to an io error while reading directory stats.", msg),
506                Errno::ELOOP => (LoopInSymbolicLinks, "{} due to a symbolic link loop in the path.", msg),
507                Errno::ENOTDIR => (PathPrefixIsNotADirectory, "{} since the path prefix is not a directory.", msg),
508                Errno::EOVERFLOW => (DataOverflowInStatStruct, "{} since certain properties like size would cause an overflow in the underlying stat struct.", msg),
509                v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
510            );
511        }
512
513        Ok(buffer.st_mode & S_IFDIR != 0)
514    }
515
516    fn acquire_metadata(&self, file: &FileName, msg: &str) -> Result<Metadata, DirectoryStatError> {
517        let mut buffer = posix::stat_t::new_zeroed();
518        let mut path = *self.path();
519        path.push(PATH_SEPARATOR).unwrap();
520        path.push_bytes(file.as_bytes()).unwrap();
521
522        if unsafe { posix::stat(path.as_c_str(), &mut buffer) } == -1 {
523            handle_errno!(DirectoryStatError, from self,
524                Errno::EACCES => (InsufficientPermissions, "{} due to insufficient permissions to open path.", msg),
525                Errno::EIO => (IOerror, "{} due to an io error while reading directory stats.", msg),
526                Errno::ELOOP => (LoopInSymbolicLinks, "{} due to a symbolic link loop in the path.", msg),
527                Errno::ENOENT => (DoesNotExist, "{} since the path does not exist.", msg),
528                Errno::ENOTDIR => (PathPrefixIsNotADirectory, "{} since the path prefix is not a directory.", msg),
529                Errno::EOVERFLOW => (DataOverflowInStatStruct, "{} since certain properties like size would cause an overflow in the underlying stat struct.", msg),
530                v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
531            );
532        }
533
534        Ok(Metadata::create(&buffer))
535    }
536}
537
538impl FileDescriptorBased for Directory {
539    fn file_descriptor(&self) -> &FileDescriptor {
540        &self.file_descriptor
541    }
542}
543
544impl FileDescriptorManagement for Directory {}