fs.rs

  1use anyhow::{anyhow, Result};
  2use fsevent::EventStream;
  3use futures::{Stream, StreamExt};
  4use smol::io::{AsyncReadExt, AsyncWriteExt};
  5use std::{
  6    io,
  7    os::unix::fs::MetadataExt,
  8    path::{Component, Path, PathBuf},
  9    pin::Pin,
 10    time::{Duration, SystemTime},
 11};
 12use text::Rope;
 13
 14#[async_trait::async_trait]
 15pub trait Fs: Send + Sync {
 16    async fn create_dir(&self, path: &Path) -> Result<()>;
 17    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;
 18    async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()>;
 19    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>;
 20    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>;
 21    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
 22    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
 23    async fn load(&self, path: &Path) -> Result<String>;
 24    async fn save(&self, path: &Path, text: &Rope) -> Result<()>;
 25    async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
 26    async fn is_file(&self, path: &Path) -> bool;
 27    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>;
 28    async fn read_dir(
 29        &self,
 30        path: &Path,
 31    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;
 32    async fn watch(
 33        &self,
 34        path: &Path,
 35        latency: Duration,
 36    ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>;
 37    fn is_fake(&self) -> bool;
 38    #[cfg(any(test, feature = "test-support"))]
 39    fn as_fake(&self) -> &FakeFs;
 40}
 41
 42#[derive(Copy, Clone, Default)]
 43pub struct CreateOptions {
 44    pub overwrite: bool,
 45    pub ignore_if_exists: bool,
 46}
 47
 48#[derive(Copy, Clone, Default)]
 49pub struct CopyOptions {
 50    pub overwrite: bool,
 51    pub ignore_if_exists: bool,
 52}
 53
 54#[derive(Copy, Clone, Default)]
 55pub struct RenameOptions {
 56    pub overwrite: bool,
 57    pub ignore_if_exists: bool,
 58}
 59
 60#[derive(Copy, Clone, Default)]
 61pub struct RemoveOptions {
 62    pub recursive: bool,
 63    pub ignore_if_not_exists: bool,
 64}
 65
 66#[derive(Clone, Debug)]
 67pub struct Metadata {
 68    pub inode: u64,
 69    pub mtime: SystemTime,
 70    pub is_symlink: bool,
 71    pub is_dir: bool,
 72}
 73
 74pub struct RealFs;
 75
 76#[async_trait::async_trait]
 77impl Fs for RealFs {
 78    async fn create_dir(&self, path: &Path) -> Result<()> {
 79        Ok(smol::fs::create_dir_all(path).await?)
 80    }
 81
 82    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
 83        let mut open_options = smol::fs::OpenOptions::new();
 84        open_options.write(true).create(true);
 85        if options.overwrite {
 86            open_options.truncate(true);
 87        } else if !options.ignore_if_exists {
 88            open_options.create_new(true);
 89        }
 90        open_options.open(path).await?;
 91        Ok(())
 92    }
 93
 94    async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
 95        if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
 96            if options.ignore_if_exists {
 97                return Ok(());
 98            } else {
 99                return Err(anyhow!("{target:?} already exists"));
100            }
101        }
102
103        let metadata = smol::fs::metadata(source).await?;
104        let _ = smol::fs::remove_dir_all(target).await;
105        if metadata.is_dir() {
106            self.create_dir(target).await?;
107            let mut children = smol::fs::read_dir(source).await?;
108            while let Some(child) = children.next().await {
109                if let Ok(child) = child {
110                    let child_source_path = child.path();
111                    let child_target_path = target.join(child.file_name());
112                    self.copy(&child_source_path, &child_target_path, options)
113                        .await?;
114                }
115            }
116        } else {
117            smol::fs::copy(source, target).await?;
118        }
119
120        Ok(())
121    }
122
123    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
124        if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
125            if options.ignore_if_exists {
126                return Ok(());
127            } else {
128                return Err(anyhow!("{target:?} already exists"));
129            }
130        }
131
132        smol::fs::rename(source, target).await?;
133        Ok(())
134    }
135
136    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
137        let result = if options.recursive {
138            smol::fs::remove_dir_all(path).await
139        } else {
140            smol::fs::remove_dir(path).await
141        };
142        match result {
143            Ok(()) => Ok(()),
144            Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
145                Ok(())
146            }
147            Err(err) => Err(err)?,
148        }
149    }
150
151    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
152        match smol::fs::remove_file(path).await {
153            Ok(()) => Ok(()),
154            Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
155                Ok(())
156            }
157            Err(err) => Err(err)?,
158        }
159    }
160
161    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
162        Ok(Box::new(std::fs::File::open(path)?))
163    }
164
165    async fn load(&self, path: &Path) -> Result<String> {
166        let mut file = smol::fs::File::open(path).await?;
167        let mut text = String::new();
168        file.read_to_string(&mut text).await?;
169        Ok(text)
170    }
171
172    async fn save(&self, path: &Path, text: &Rope) -> Result<()> {
173        let buffer_size = text.summary().bytes.min(10 * 1024);
174        let file = smol::fs::File::create(path).await?;
175        let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
176        for chunk in text.chunks() {
177            writer.write_all(chunk.as_bytes()).await?;
178        }
179        writer.flush().await?;
180        Ok(())
181    }
182
183    async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
184        Ok(smol::fs::canonicalize(path).await?)
185    }
186
187    async fn is_file(&self, path: &Path) -> bool {
188        smol::fs::metadata(path)
189            .await
190            .map_or(false, |metadata| metadata.is_file())
191    }
192
193    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
194        let symlink_metadata = match smol::fs::symlink_metadata(path).await {
195            Ok(metadata) => metadata,
196            Err(err) => {
197                return match (err.kind(), err.raw_os_error()) {
198                    (io::ErrorKind::NotFound, _) => Ok(None),
199                    (io::ErrorKind::Other, Some(libc::ENOTDIR)) => Ok(None),
200                    _ => Err(anyhow::Error::new(err)),
201                }
202            }
203        };
204
205        let is_symlink = symlink_metadata.file_type().is_symlink();
206        let metadata = if is_symlink {
207            smol::fs::metadata(path).await?
208        } else {
209            symlink_metadata
210        };
211        Ok(Some(Metadata {
212            inode: metadata.ino(),
213            mtime: metadata.modified().unwrap(),
214            is_symlink,
215            is_dir: metadata.file_type().is_dir(),
216        }))
217    }
218
219    async fn read_dir(
220        &self,
221        path: &Path,
222    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
223        let result = smol::fs::read_dir(path).await?.map(|entry| match entry {
224            Ok(entry) => Ok(entry.path()),
225            Err(error) => Err(anyhow!("failed to read dir entry {:?}", error)),
226        });
227        Ok(Box::pin(result))
228    }
229
230    async fn watch(
231        &self,
232        path: &Path,
233        latency: Duration,
234    ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
235        let (tx, rx) = smol::channel::unbounded();
236        let (stream, handle) = EventStream::new(&[path], latency);
237        std::mem::forget(handle);
238        std::thread::spawn(move || {
239            stream.run(move |events| smol::block_on(tx.send(events)).is_ok());
240        });
241        Box::pin(rx)
242    }
243
244    fn is_fake(&self) -> bool {
245        false
246    }
247    #[cfg(any(test, feature = "test-support"))]
248    fn as_fake(&self) -> &FakeFs {
249        panic!("called `RealFs::as_fake`")
250    }
251}
252
253#[cfg(any(test, feature = "test-support"))]
254#[derive(Clone, Debug)]
255struct FakeFsEntry {
256    metadata: Metadata,
257    content: Option<String>,
258}
259
260#[cfg(any(test, feature = "test-support"))]
261struct FakeFsState {
262    entries: std::collections::BTreeMap<PathBuf, FakeFsEntry>,
263    next_inode: u64,
264    event_txs: Vec<smol::channel::Sender<Vec<fsevent::Event>>>,
265}
266
267#[cfg(any(test, feature = "test-support"))]
268impl FakeFsState {
269    fn validate_path(&self, path: &Path) -> Result<()> {
270        if path.is_absolute()
271            && path
272                .parent()
273                .and_then(|path| self.entries.get(path))
274                .map_or(false, |e| e.metadata.is_dir)
275        {
276            Ok(())
277        } else {
278            Err(anyhow!("invalid path {:?}", path))
279        }
280    }
281
282    async fn emit_event<I, T>(&mut self, paths: I)
283    where
284        I: IntoIterator<Item = T>,
285        T: Into<PathBuf>,
286    {
287        let events = paths
288            .into_iter()
289            .map(|path| fsevent::Event {
290                event_id: 0,
291                flags: fsevent::StreamFlags::empty(),
292                path: path.into(),
293            })
294            .collect::<Vec<_>>();
295
296        self.event_txs.retain(|tx| {
297            let _ = tx.try_send(events.clone());
298            !tx.is_closed()
299        });
300    }
301}
302
303#[cfg(any(test, feature = "test-support"))]
304pub struct FakeFs {
305    // Use an unfair lock to ensure tests are deterministic.
306    state: futures::lock::Mutex<FakeFsState>,
307    executor: std::sync::Weak<gpui::executor::Background>,
308}
309
310#[cfg(any(test, feature = "test-support"))]
311impl FakeFs {
312    pub fn new(executor: std::sync::Arc<gpui::executor::Background>) -> std::sync::Arc<Self> {
313        let mut entries = std::collections::BTreeMap::new();
314        entries.insert(
315            Path::new("/").to_path_buf(),
316            FakeFsEntry {
317                metadata: Metadata {
318                    inode: 0,
319                    mtime: SystemTime::now(),
320                    is_dir: true,
321                    is_symlink: false,
322                },
323                content: None,
324            },
325        );
326        std::sync::Arc::new(Self {
327            executor: std::sync::Arc::downgrade(&executor),
328            state: futures::lock::Mutex::new(FakeFsState {
329                entries,
330                next_inode: 1,
331                event_txs: Default::default(),
332            }),
333        })
334    }
335
336    pub async fn insert_dir(&self, path: impl AsRef<Path>) {
337        let mut state = self.state.lock().await;
338        let path = path.as_ref();
339        state.validate_path(path).unwrap();
340
341        let inode = state.next_inode;
342        state.next_inode += 1;
343        state.entries.insert(
344            path.to_path_buf(),
345            FakeFsEntry {
346                metadata: Metadata {
347                    inode,
348                    mtime: SystemTime::now(),
349                    is_dir: true,
350                    is_symlink: false,
351                },
352                content: None,
353            },
354        );
355        state.emit_event(&[path]).await;
356    }
357
358    pub async fn insert_file(&self, path: impl AsRef<Path>, content: String) {
359        let mut state = self.state.lock().await;
360        let path = path.as_ref();
361        state.validate_path(path).unwrap();
362
363        let inode = state.next_inode;
364        state.next_inode += 1;
365        state.entries.insert(
366            path.to_path_buf(),
367            FakeFsEntry {
368                metadata: Metadata {
369                    inode,
370                    mtime: SystemTime::now(),
371                    is_dir: false,
372                    is_symlink: false,
373                },
374                content: Some(content),
375            },
376        );
377        state.emit_event(&[path]).await;
378    }
379
380    #[must_use]
381    pub fn insert_tree<'a>(
382        &'a self,
383        path: impl 'a + AsRef<Path> + Send,
384        tree: serde_json::Value,
385    ) -> futures::future::BoxFuture<'a, ()> {
386        use futures::FutureExt as _;
387        use serde_json::Value::*;
388
389        async move {
390            let path = path.as_ref();
391
392            match tree {
393                Object(map) => {
394                    self.insert_dir(path).await;
395                    for (name, contents) in map {
396                        let mut path = PathBuf::from(path);
397                        path.push(name);
398                        self.insert_tree(&path, contents).await;
399                    }
400                }
401                Null => {
402                    self.insert_dir(&path).await;
403                }
404                String(contents) => {
405                    self.insert_file(&path, contents).await;
406                }
407                _ => {
408                    panic!("JSON object must contain only objects, strings, or null");
409                }
410            }
411        }
412        .boxed()
413    }
414
415    pub async fn files(&self) -> Vec<PathBuf> {
416        self.state
417            .lock()
418            .await
419            .entries
420            .iter()
421            .filter_map(|(path, entry)| entry.content.as_ref().map(|_| path.clone()))
422            .collect()
423    }
424
425    async fn simulate_random_delay(&self) {
426        self.executor
427            .upgrade()
428            .expect("executor has been dropped")
429            .simulate_random_delay()
430            .await;
431    }
432}
433
434#[cfg(any(test, feature = "test-support"))]
435#[async_trait::async_trait]
436impl Fs for FakeFs {
437    async fn create_dir(&self, path: &Path) -> Result<()> {
438        self.simulate_random_delay().await;
439        let state = &mut *self.state.lock().await;
440        let path = normalize_path(path);
441        let mut ancestor_path = PathBuf::new();
442        let mut created_dir_paths = Vec::new();
443        for component in path.components() {
444            ancestor_path.push(component);
445            let entry = state
446                .entries
447                .entry(ancestor_path.clone())
448                .or_insert_with(|| {
449                    let inode = state.next_inode;
450                    state.next_inode += 1;
451                    created_dir_paths.push(ancestor_path.clone());
452                    FakeFsEntry {
453                        metadata: Metadata {
454                            inode,
455                            mtime: SystemTime::now(),
456                            is_dir: true,
457                            is_symlink: false,
458                        },
459                        content: None,
460                    }
461                });
462            if !entry.metadata.is_dir {
463                return Err(anyhow!(
464                    "cannot create directory because {:?} is a file",
465                    ancestor_path
466                ));
467            }
468        }
469        state.emit_event(&created_dir_paths).await;
470
471        Ok(())
472    }
473
474    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
475        self.simulate_random_delay().await;
476        let mut state = self.state.lock().await;
477        let path = normalize_path(path);
478        state.validate_path(&path)?;
479        if let Some(entry) = state.entries.get_mut(&path) {
480            if entry.metadata.is_dir || entry.metadata.is_symlink {
481                return Err(anyhow!(
482                    "cannot create file because {:?} is a dir or a symlink",
483                    path
484                ));
485            }
486
487            if options.overwrite {
488                entry.metadata.mtime = SystemTime::now();
489                entry.content = Some(Default::default());
490            } else if !options.ignore_if_exists {
491                return Err(anyhow!(
492                    "cannot create file because {:?} already exists",
493                    &path
494                ));
495            }
496        } else {
497            let inode = state.next_inode;
498            state.next_inode += 1;
499            let entry = FakeFsEntry {
500                metadata: Metadata {
501                    inode,
502                    mtime: SystemTime::now(),
503                    is_dir: false,
504                    is_symlink: false,
505                },
506                content: Some(Default::default()),
507            };
508            state.entries.insert(path.to_path_buf(), entry);
509        }
510        state.emit_event(&[path]).await;
511
512        Ok(())
513    }
514
515    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
516        let source = normalize_path(source);
517        let target = normalize_path(target);
518
519        let mut state = self.state.lock().await;
520        state.validate_path(&source)?;
521        state.validate_path(&target)?;
522
523        if !options.overwrite && state.entries.contains_key(&target) {
524            if options.ignore_if_exists {
525                return Ok(());
526            } else {
527                return Err(anyhow!("{target:?} already exists"));
528            }
529        }
530
531        let mut removed = Vec::new();
532        state.entries.retain(|path, entry| {
533            if let Ok(relative_path) = path.strip_prefix(&source) {
534                removed.push((relative_path.to_path_buf(), entry.clone()));
535                false
536            } else {
537                true
538            }
539        });
540
541        for (relative_path, entry) in removed {
542            let new_path = normalize_path(&target.join(relative_path));
543            state.entries.insert(new_path, entry);
544        }
545
546        state.emit_event(&[source, target]).await;
547        Ok(())
548    }
549
550    async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
551        let source = normalize_path(source);
552        let target = normalize_path(target);
553
554        let mut state = self.state.lock().await;
555        state.validate_path(&source)?;
556        state.validate_path(&target)?;
557
558        if !options.overwrite && state.entries.contains_key(&target) {
559            if options.ignore_if_exists {
560                return Ok(());
561            } else {
562                return Err(anyhow!("{target:?} already exists"));
563            }
564        }
565
566        let mut new_entries = Vec::new();
567        for (path, entry) in &state.entries {
568            if let Ok(relative_path) = path.strip_prefix(&source) {
569                new_entries.push((relative_path.to_path_buf(), entry.clone()));
570            }
571        }
572
573        let mut events = Vec::new();
574        for (relative_path, entry) in new_entries {
575            let new_path = normalize_path(&target.join(relative_path));
576            events.push(new_path.clone());
577            state.entries.insert(new_path, entry);
578        }
579
580        state.emit_event(&events).await;
581        Ok(())
582    }
583
584    async fn remove_dir(&self, dir_path: &Path, options: RemoveOptions) -> Result<()> {
585        let dir_path = normalize_path(dir_path);
586        let mut state = self.state.lock().await;
587        state.validate_path(&dir_path)?;
588        if let Some(entry) = state.entries.get(&dir_path) {
589            if !entry.metadata.is_dir {
590                return Err(anyhow!(
591                    "cannot remove {dir_path:?} because it is not a dir"
592                ));
593            }
594
595            if !options.recursive {
596                let descendants = state
597                    .entries
598                    .keys()
599                    .filter(|path| path.starts_with(path))
600                    .count();
601                if descendants > 1 {
602                    return Err(anyhow!("{dir_path:?} is not empty"));
603                }
604            }
605
606            state.entries.retain(|path, _| !path.starts_with(&dir_path));
607            state.emit_event(&[dir_path]).await;
608        } else if !options.ignore_if_not_exists {
609            return Err(anyhow!("{dir_path:?} does not exist"));
610        }
611
612        Ok(())
613    }
614
615    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
616        let path = normalize_path(path);
617        let mut state = self.state.lock().await;
618        state.validate_path(&path)?;
619        if let Some(entry) = state.entries.get(&path) {
620            if entry.metadata.is_dir {
621                return Err(anyhow!("cannot remove {path:?} because it is not a file"));
622            }
623
624            state.entries.remove(&path);
625            state.emit_event(&[path]).await;
626        } else if !options.ignore_if_not_exists {
627            return Err(anyhow!("{path:?} does not exist"));
628        }
629        Ok(())
630    }
631
632    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
633        let text = self.load(path).await?;
634        Ok(Box::new(io::Cursor::new(text)))
635    }
636
637    async fn load(&self, path: &Path) -> Result<String> {
638        let path = normalize_path(path);
639        self.simulate_random_delay().await;
640        let state = self.state.lock().await;
641        let text = state
642            .entries
643            .get(&path)
644            .and_then(|e| e.content.as_ref())
645            .ok_or_else(|| anyhow!("file {:?} does not exist", path))?;
646        Ok(text.clone())
647    }
648
649    async fn save(&self, path: &Path, text: &Rope) -> Result<()> {
650        self.simulate_random_delay().await;
651        let mut state = self.state.lock().await;
652        let path = normalize_path(path);
653        state.validate_path(&path)?;
654        if let Some(entry) = state.entries.get_mut(&path) {
655            if entry.metadata.is_dir {
656                Err(anyhow!("cannot overwrite a directory with a file"))
657            } else {
658                entry.content = Some(text.chunks().collect());
659                entry.metadata.mtime = SystemTime::now();
660                state.emit_event(&[path]).await;
661                Ok(())
662            }
663        } else {
664            let inode = state.next_inode;
665            state.next_inode += 1;
666            let entry = FakeFsEntry {
667                metadata: Metadata {
668                    inode,
669                    mtime: SystemTime::now(),
670                    is_dir: false,
671                    is_symlink: false,
672                },
673                content: Some(text.chunks().collect()),
674            };
675            state.entries.insert(path.to_path_buf(), entry);
676            state.emit_event(&[path]).await;
677            Ok(())
678        }
679    }
680
681    async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
682        self.simulate_random_delay().await;
683        Ok(normalize_path(path))
684    }
685
686    async fn is_file(&self, path: &Path) -> bool {
687        let path = normalize_path(path);
688        self.simulate_random_delay().await;
689        let state = self.state.lock().await;
690        state
691            .entries
692            .get(&path)
693            .map_or(false, |entry| !entry.metadata.is_dir)
694    }
695
696    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
697        self.simulate_random_delay().await;
698        let state = self.state.lock().await;
699        let path = normalize_path(path);
700        Ok(state.entries.get(&path).map(|entry| entry.metadata.clone()))
701    }
702
703    async fn read_dir(
704        &self,
705        abs_path: &Path,
706    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
707        use futures::{future, stream};
708        self.simulate_random_delay().await;
709        let state = self.state.lock().await;
710        let abs_path = normalize_path(abs_path);
711        Ok(Box::pin(stream::iter(state.entries.clone()).filter_map(
712            move |(child_path, _)| {
713                future::ready(if child_path.parent() == Some(&abs_path) {
714                    Some(Ok(child_path))
715                } else {
716                    None
717                })
718            },
719        )))
720    }
721
722    async fn watch(
723        &self,
724        path: &Path,
725        _: Duration,
726    ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
727        let mut state = self.state.lock().await;
728        self.simulate_random_delay().await;
729        let (tx, rx) = smol::channel::unbounded();
730        state.event_txs.push(tx);
731        let path = path.to_path_buf();
732        let executor = self.executor.clone();
733        Box::pin(futures::StreamExt::filter(rx, move |events| {
734            let result = events.iter().any(|event| event.path.starts_with(&path));
735            let executor = executor.clone();
736            async move {
737                if let Some(executor) = executor.clone().upgrade() {
738                    executor.simulate_random_delay().await;
739                }
740                result
741            }
742        }))
743    }
744
745    fn is_fake(&self) -> bool {
746        true
747    }
748
749    #[cfg(any(test, feature = "test-support"))]
750    fn as_fake(&self) -> &FakeFs {
751        self
752    }
753}
754
755pub fn normalize_path(path: &Path) -> PathBuf {
756    let mut components = path.components().peekable();
757    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
758        components.next();
759        PathBuf::from(c.as_os_str())
760    } else {
761        PathBuf::new()
762    };
763
764    for component in components {
765        match component {
766            Component::Prefix(..) => unreachable!(),
767            Component::RootDir => {
768                ret.push(component.as_os_str());
769            }
770            Component::CurDir => {}
771            Component::ParentDir => {
772                ret.pop();
773            }
774            Component::Normal(c) => {
775                ret.push(c);
776            }
777        }
778    }
779    ret
780}