/ src / objectpath.rs
objectpath.rs
  1  use std::path::{Path, PathBuf};
  2  use std::ffi::OsStr;
  3  use std::sync::{Arc, Weak};
  4  use std::sync::atomic::{self, AtomicBool};
  5  use std::io;
  6  
  7  use parking_lot::Mutex;
  8  use derivative::Derivative;
  9  
 10  #[allow(unused_imports)]
 11  use crate::{debug, error, info, trace, warn};
 12  use crate::{Dir, Gatherer, GathererInner, InternedName};
 13  
 14  /// Space efficient storage of paths. Instead storing full path-names it stores only interned
 15  /// strings of the actual object names and a reference to its parent. ObjectPaths are reference counted.
 16  #[derive(Derivative)]
 17  #[derivative(Hash, PartialOrd, PartialEq, Ord, Eq)]
 18  pub struct ObjectPath(Arc<ObjectPathInner>);
 19  
 20  impl ObjectPath {
 21      /// Creates a new ObjectPath without a parent.
 22      // pub fn new<P: AsRef<Path>>(path: P, gatherer: &Gatherer) -> ObjectPath {
 23      pub fn new(path: impl AsRef<Path>, gatherer: &Gatherer) -> ObjectPath {
 24          ObjectPath(Arc::new(ObjectPathInner {
 25              parent:   None,
 26              name:     gatherer.name_interning(path.as_ref().as_os_str()),
 27              dir:      Mutex::new(None),
 28              gatherer: gatherer.downgrade(),
 29              watched:  AtomicBool::new(false),
 30          }))
 31      }
 32  
 33      /// Creates a new ObjectPath without a parent and associated gatherer.
 34      pub fn without_gatherer<P: AsRef<Path>>(path: P) -> ObjectPath {
 35          ObjectPath(Arc::new(ObjectPathInner {
 36              parent:   None,
 37              name:     InternedName::new(path.as_ref().as_os_str()),
 38              dir:      Mutex::new(None),
 39              gatherer: Weak::new(),
 40              watched:  AtomicBool::new(false),
 41          }))
 42      }
 43  
 44      /// Creates a new ObjectPath as sub-object to some existing ObjectPath object.
 45      #[must_use]
 46      pub fn sub_object<P: AsRef<Path>>(&self, name: P, gatherer: &Gatherer) -> ObjectPath {
 47          ObjectPath(Arc::new(ObjectPathInner {
 48              parent:   Some(self.clone()),
 49              name:     gatherer.name_interning(name.as_ref().as_os_str()),
 50              dir:      Mutex::new(None),
 51              gatherer: gatherer.downgrade(),
 52              watched:  AtomicBool::new(false),
 53          }))
 54      }
 55  
 56      /// Creates a new ObjectPath as sub-object to some existing ObjectPath object without
 57      /// associated gatherer.
 58      #[must_use]
 59      pub fn sub_object_without_gatherer<P: AsRef<Path>>(&self, name: P) -> ObjectPath {
 60          ObjectPath(Arc::new(ObjectPathInner {
 61              parent:   Some(self.clone()),
 62              name:     InternedName::new(name.as_ref().as_os_str()),
 63              dir:      Mutex::new(None),
 64              gatherer: Weak::new(),
 65              watched:  AtomicBool::new(false),
 66          }))
 67      }
 68  
 69      fn pathbuf_push_parents(&self, target: &mut PathBuf, len: usize) {
 70          if let Some(parent) = &self.0.parent {
 71              parent.pathbuf_push_parents(
 72                  target,
 73                  len + self.0.name.len() + 1, // delimiter char
 74              )
 75          } else {
 76              target.reserve(len + self.0.name.len());
 77          };
 78          target.push(&*self.0.name);
 79      }
 80  
 81      /// Writes the full ObjectPath including all parents to the given PathBuf.
 82      pub fn write_pathbuf<'a>(&self, target: &'a mut PathBuf) -> &'a PathBuf {
 83          target.clear();
 84          self.pathbuf_push_parents(target, 1 /* for root delimiter */);
 85          target
 86      }
 87  
 88      /// Create a new PathBuf from the given ObjectPath.
 89      pub fn to_pathbuf(&self) -> PathBuf {
 90          // TODO: iterative impl
 91          let mut target = PathBuf::new();
 92          self.pathbuf_push_parents(&mut target, 1 /* for root delimiter */);
 93          target
 94      }
 95  
 96      // Returns path length in bytes including delimiters.
 97      // pub fn len(&self) -> usize {
 98      //
 99      // }
100  
101      /// Returns the number of components in the path.
102      pub fn depth(&self) -> u16 {
103          let mut counter = 1u16;
104          let mut itr = &self.0;
105          while let Some(parent) = &itr.parent {
106              itr = &parent.0;
107              counter += 1;
108          }
109          counter
110      }
111  
112      /// Returns an reference to the name of the object, without any preceding path components.
113      pub fn name(&self) -> &OsStr {
114          &self.0.name
115      }
116  
117      /// Return the metadata of an objectpath
118      pub fn metadata(&self) -> std::io::Result<crate::openat::Metadata> {
119          // FIXME: use parents dir handle if available
120          let parent = if let Some(parent) = &self.0.parent {
121              parent.to_pathbuf()
122          } else {
123              PathBuf::from(if Path::new(&*self.0.name).is_absolute() {
124                  std::path::Component::RootDir.as_os_str()
125              } else {
126                  std::path::Component::CurDir.as_os_str()
127              })
128          };
129  
130          crate::openat::Dir::open(&parent)?.metadata(&*self.0.name)
131      }
132  
133      /// Returns the number of strong references pointing to this object
134      pub fn strong_count(&self) -> usize {
135          Arc::strong_count(&self.0)
136      }
137  
138      /// Sets the notification state on this ObjectPath. When true and all processing handles get
139      /// dropped a notification is issued. Watching an ObjectPath also retains its dir handle.
140      pub fn watch(&self, watch: bool) {
141          self.0.watched.store(watch, atomic::Ordering::Relaxed);
142      }
143  
144      /// Queries the notification state on this ObjectPath. When true and all processing handles get
145      /// dropped a notification is issued.
146      pub fn is_watched(&self) -> bool {
147          self.0.watched.load(atomic::Ordering::Relaxed)
148      }
149  
150      /// Gets a dir handle for this object.
151      pub fn dir(&self) -> io::Result<Arc<Dir>> {
152          let mut locked_dir = self.0.dir.lock();
153  
154          Ok(match locked_dir.as_ref().map(Weak::upgrade) {
155              Some(Some(arc)) => {
156                  trace!("OPEN reuse handle {:?}", self);
157                  arc
158              }
159              _ => {
160                  let dir_arc = Arc::new(
161                      match self
162                          .parent()
163                          .map(|parent| parent.0.dir.lock().as_ref().map(Weak::upgrade))
164                      {
165                          Some(Some(Some(parent_handle))) => {
166                              trace!("OPEN subdir {:?}", self);
167                              parent_handle.sub_dir(self.name())?
168                          }
169                          _ => {
170                              trace!("OPEN new {:?}", self);
171                              Dir::open(&self.to_pathbuf())?
172                          }
173                      },
174                  );
175                  *locked_dir = Some(Arc::downgrade(&dir_arc));
176  
177                  dir_arc
178              }
179          })
180      }
181  
182      /// Returns an Arc handle to the Gatherer if available
183      pub fn gatherer(&self) -> Option<Gatherer> {
184          Gatherer::upgrade(&self.0.gatherer)
185      }
186  
187      /// Returns the parent if available
188      pub fn parent(&self) -> Option<&ObjectPath> {
189          self.0.parent.as_ref()
190      }
191  }
192  
193  impl Clone for ObjectPath {
194      fn clone(&self) -> Self {
195          ObjectPath(self.0.clone())
196      }
197  }
198  
199  impl Drop for ObjectPath {
200      fn drop(&mut self) {
201          // Ordering::Relaxed suffices here because the last reference is always the gatherer
202          // itself (which starts with two references). The gatherer will never increase the
203          // refcount when it becomes dropped to one. Disable watching, so that notify() can
204          // clone the path without retriggering the notifier. 2 because this is the refcount
205          // before this drop happend.
206          if self.strong_count() <= 2 && self.0.watched.swap(false, atomic::Ordering::Relaxed) {
207              if let Some(gatherer) = self.gatherer() {
208                  gatherer.notify_path_dropped(self.clone());
209              }
210          }
211      }
212  }
213  
214  #[derive(Derivative)]
215  #[derivative(Hash, PartialOrd, PartialEq, Ord, Eq)]
216  pub struct ObjectPathInner {
217      parent: Option<ObjectPath>,
218      name:   InternedName,
219  
220      #[derivative(
221          Hash = "ignore",
222          PartialEq = "ignore",
223          PartialOrd = "ignore",
224          Ord = "ignore"
225      )]
226      dir: Mutex<Option<Weak<Dir>>>,
227  
228      #[derivative(
229          Hash = "ignore",
230          PartialEq = "ignore",
231          PartialOrd = "ignore",
232          Ord = "ignore"
233      )]
234      gatherer: Weak<GathererInner>,
235  
236      #[derivative(
237          Hash = "ignore",
238          PartialEq = "ignore",
239          PartialOrd = "ignore",
240          Ord = "ignore"
241      )]
242      watched: AtomicBool,
243  }
244  
245  use std::fmt;
246  impl fmt::Debug for ObjectPath {
247      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248          write!(f, "{:?}", self.to_pathbuf())
249      }
250  }
251  
252  #[cfg(test)]
253  mod test {
254      use std::path::PathBuf;
255      use std::ffi::OsStr;
256  
257      #[allow(unused_imports)]
258      use crate::{debug, error, info, trace, warn};
259      use crate::InternedName;
260      use super::ObjectPath;
261  
262      #[test]
263      fn smoke() {
264          crate::test::init_env_logging();
265          assert_eq!(
266              ObjectPath::without_gatherer(".").to_pathbuf(),
267              PathBuf::from(".")
268          );
269      }
270  
271      #[test]
272      fn path_subobject() {
273          crate::test::init_env_logging();
274          use std::ffi::OsStr;
275          let p = ObjectPath::without_gatherer(".");
276          let mut pathbuf = PathBuf::new();
277          assert_eq!(
278              p.sub_object_without_gatherer(InternedName::new(OsStr::new("foo")))
279                  .write_pathbuf(&mut pathbuf),
280              &PathBuf::from("./foo")
281          );
282      }
283  
284      #[test]
285      fn path_ordering() {
286          crate::test::init_env_logging();
287          let foo = ObjectPath::without_gatherer("foo");
288          let bar = ObjectPath::without_gatherer("bar");
289          assert!(bar < foo);
290  
291          let bar2 = ObjectPath::without_gatherer("bar");
292          assert!(bar == bar2);
293  
294          let foobar = foo.sub_object_without_gatherer(InternedName::new(OsStr::new("bar")));
295          let barfoo = bar.sub_object_without_gatherer(InternedName::new(OsStr::new("foo")));
296          assert!(barfoo < foobar);
297      }
298  
299      #[test]
300      fn metadata() {
301          crate::test::init_env_logging();
302          let cargo = ObjectPath::without_gatherer("Cargo.toml");
303          assert!(cargo.metadata().is_ok());
304      }
305  }