indexedlog/log/
path.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8use std::path::PathBuf;
9use std::sync::Arc;
10use std::sync::Mutex;
11
12use crate::lock::ScopedDirLock;
13use crate::log::LogMetadata;
14use crate::log::META_FILE;
15use crate::utils;
16
17/// Abstract Path for [`Log`].
18///
19/// This defines where a [`Log`] reads and writes data.
20#[derive(Clone, Debug)]
21pub enum GenericPath {
22    /// The [`Log`] is backed by a directory on filesystem.
23    Filesystem(PathBuf),
24
25    /// Metadata is shared (between `Log` and `MultiLog`).
26    /// Other parts still use `path`.
27    SharedMeta {
28        path: Box<GenericPath>,
29        meta: Arc<Mutex<LogMetadata>>,
30    },
31
32    /// From nothing. Indicates creating from memory.
33    Nothing,
34}
35
36impl From<&std::path::Path> for GenericPath {
37    fn from(path: &std::path::Path) -> Self {
38        Self::Filesystem(path.to_path_buf())
39    }
40}
41
42impl From<&str> for GenericPath {
43    fn from(path: &str) -> Self {
44        Self::Filesystem(std::path::Path::new(path).to_path_buf())
45    }
46}
47
48impl From<PathBuf> for GenericPath {
49    fn from(path: PathBuf) -> Self {
50        Self::Filesystem(path)
51    }
52}
53
54impl From<&PathBuf> for GenericPath {
55    fn from(path: &PathBuf) -> Self {
56        Self::Filesystem(path.clone())
57    }
58}
59
60impl From<()> for GenericPath {
61    fn from(_path: ()) -> Self {
62        Self::Nothing
63    }
64}
65
66impl GenericPath {
67    /// Return the main filesystem path.
68    pub fn as_opt_path(&self) -> Option<&std::path::Path> {
69        match self {
70            GenericPath::Filesystem(path) => Some(path),
71            GenericPath::SharedMeta { path, .. } => path.as_opt_path(),
72            GenericPath::Nothing => None,
73        }
74    }
75
76    pub(crate) fn mkdir(&self) -> crate::Result<()> {
77        if let Some(dir) = self.as_opt_path() {
78            utils::mkdir_p(dir)
79        } else {
80            Ok(())
81        }
82    }
83
84    pub(crate) fn lock(&self) -> crate::Result<ScopedDirLock> {
85        if let Some(dir) = self.as_opt_path() {
86            Ok(ScopedDirLock::new(dir)?)
87        } else {
88            Err(crate::Error::programming(
89                "read_meta() does not support GenericPath::Nothing",
90            ))
91        }
92    }
93
94    pub(crate) fn read_meta(&self) -> crate::Result<LogMetadata> {
95        match self {
96            GenericPath::Filesystem(dir) => {
97                let meta_path = dir.join(META_FILE);
98                LogMetadata::read_file(meta_path)
99            }
100            GenericPath::SharedMeta { meta, path } => {
101                let meta = meta.lock().unwrap();
102                if let GenericPath::Filesystem(dir) = path.as_ref() {
103                    let meta_path = dir.join(META_FILE);
104                    if let Ok(on_disk_meta) = LogMetadata::read_file(meta_path) {
105                        // Prefer the per-log "meta" if it is compatible with the multi-meta.
106                        // The per-log meta might contain more up-to-date information about
107                        // indexes, etc.
108                        if meta.is_compatible_with(&on_disk_meta) {
109                            return Ok(on_disk_meta);
110                        }
111                    }
112                }
113                Ok(meta.clone())
114            }
115            GenericPath::Nothing => Err(crate::Error::programming(
116                "read_meta() does not support GenericPath::Nothing",
117            )),
118        }
119    }
120
121    pub(crate) fn write_meta(&self, meta: &LogMetadata, fsync: bool) -> crate::Result<()> {
122        match self {
123            GenericPath::Filesystem(dir) => {
124                let meta_path = dir.join(META_FILE);
125                meta.write_file(meta_path, fsync)?;
126                Ok(())
127            }
128            GenericPath::SharedMeta {
129                meta: shared_meta,
130                path,
131            } => {
132                // Update the per-log "meta" file. This can be useful for
133                // picking up new indexes (see test_new_index_built_only_once),
134                // or log internal data investigation.
135                if let GenericPath::Filesystem(dir) = path.as_ref() {
136                    let meta_path = dir.join(META_FILE);
137                    meta.write_file(meta_path, fsync)?;
138                }
139                let mut shared_meta = shared_meta.lock().unwrap();
140                *shared_meta = meta.clone();
141                Ok(())
142            }
143            GenericPath::Nothing => Err(crate::Error::programming(
144                "write_meta() does not support GenericPath::Nothing",
145            )),
146        }
147    }
148}