veloren_server/settings/
editable.rs

1use atomicwrites::{AtomicFile, Error as AtomicError, OverwriteBehavior};
2use core::{convert::TryInto, fmt};
3use serde::{Serialize, de::DeserializeOwned};
4use std::{
5    fs,
6    io::{Seek, Write},
7    path::{Path, PathBuf},
8};
9use tracing::{error, info, warn};
10
11/// Errors that can occur during edits to a settings file.
12pub enum Error<S: EditableSetting> {
13    /// An error occurred validating the settings file.
14    Integrity(S::Error),
15    /// An IO error occurred when writing to the settings file.
16    Io(std::io::Error),
17}
18
19impl<S: EditableSetting> fmt::Debug for Error<S> {
20    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
21        match self {
22            Error::Integrity(err) => fmt::Formatter::debug_tuple(f, "Integrity")
23                .field(err)
24                .finish(),
25            Error::Io(err) => fmt::Formatter::debug_tuple(f, "Io").field(err).finish(),
26        }
27    }
28}
29
30/// Same as Error, but carries the validated settings in the Io case.
31enum ErrorInternal<S: EditableSetting> {
32    Integrity(S::Error),
33    Io(std::io::Error, S),
34}
35
36impl<S: EditableSetting> fmt::Debug for ErrorInternal<S> {
37    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38        match self {
39            ErrorInternal::Integrity(err) => fmt::Formatter::debug_tuple(f, "Integrity")
40                .field(err)
41                .finish(),
42            ErrorInternal::Io(err, _setting) => fmt::Formatter::debug_tuple(f, "Io")
43                .field(err)
44                .field(&"EditableSetting not required to impl Debug")
45                .finish(),
46        }
47    }
48}
49
50pub enum Version {
51    /// This was an old version of the settings file, so overwrite with the
52    /// modern config.
53    Old,
54    /// Latest version of the settings file.
55    Latest,
56}
57
58pub trait EditableSetting: Clone + Default {
59    const FILENAME: &'static str;
60
61    /// Please use this error sparingly, since we ideally want to preserve
62    /// forwards compatibility for all migrations.  In particular, this
63    /// error should be used to fail validation *of the original settings
64    /// file* that cannot be caught with ordinary parsing, rather than used
65    /// to signal errors that occurred during migrations.
66    ///
67    /// The best error type is Infallible.
68    type Error: fmt::Debug;
69
70    /// `Into<Setting>` is expected to migrate directly to the latest version,
71    /// which can be implemented using "chaining".  The use of `Into` here
72    /// rather than `TryInto` is intended (together with the expected use of
73    /// chaining) to prevent migrations from invalidating old save files
74    /// without warning; there should always be a non-failing migration path
75    /// from the oldest to latest format (if the migration path fails, we can
76    /// panic).
77    type Legacy: Serialize + DeserializeOwned + Into<Self>;
78
79    /// `TryInto<(Version, Self)>` is expected to migrate to the latest version
80    /// from any older version, using "chaining" (see [super::banlist] for
81    /// examples).
82    ///
83    /// `From<Self>` is intended to construct the latest version of the
84    /// configuration file from `Self`, which we use to save the config file
85    /// on migration or modification.  Note that it should always be the
86    /// case that if x is constructed from any of `Self::clone`,
87    /// `Self::default`, or `Setting::try_into`, then
88    /// `Setting::try_from(Self::into(x)).is_ok()` must be true!
89    ///
90    /// The error should be used to fail validation *of the original settings
91    /// file* that cannot be caught with parsing.  If we can possibly avoid
92    /// it, we should not create errors in valid settings files during
93    /// migration, to ensure forwards compatibility.
94    type Setting: Serialize
95        + DeserializeOwned
96        + TryInto<(Version, Self), Error = Self::Error>
97        + From<Self>;
98
99    fn load(data_dir: &Path) -> Self {
100        let path = Self::get_path(data_dir);
101
102        if let Ok(mut file) = fs::File::open(&path) {
103            match ron::de::from_reader(&mut file)
104                .map(|setting: Self::Setting| setting.try_into())
105                .or_else(|orig_err| {
106                    file.rewind().map_err(|e| ron::error::SpannedError {
107                        code: e.into(),
108                        span: ron::error::Span {
109                            start: ron::error::Position { line: 0, col: 0 },
110                            end: ron::error::Position { line: 0, col: 0 },
111                        },
112                    })?;
113                    ron::de::from_reader(file)
114                         .map(|legacy| Ok((Version::Old, Self::Legacy::into(legacy))))
115                         // When both legacy and non-legacy have parse errors, prioritize the
116                         // non-legacy one, since we can't tell which one is "right" and legacy
117                         // formats are simple, early, and uncommon enough that we expect
118                         // few parse errors in those.
119                         .or(Err(orig_err))
120                })
121                .map_err(|e| {
122                    warn!(
123                        ?e,
124                        "Failed to parse setting file! Falling back to default and moving \
125                         existing file to a .invalid"
126                    );
127                })
128                .and_then(|inner| {
129                    inner.map_err(|e| {
130                        warn!(
131                            ?e,
132                            "Failed to parse setting file! Falling back to default and moving \
133                             existing file to a .invalid"
134                        );
135                    })
136                }) {
137                Ok((version, mut settings)) => {
138                    if matches!(version, Version::Old) {
139                        // Old version, which means we either performed a migration or there was
140                        // some needed update to the file.  If this is the case, we preemptively
141                        // overwrite the settings file (not strictly needed, but useful for
142                        // people who do manual editing).
143                        info!("Settings were changed on load, updating file...");
144                        // We don't care if we encountered an error on saving updates to a
145                        // settings file that we just loaded (it's already logged and migrated).
146                        // However, we should crash if it reported an integrity failure, since we
147                        // supposedly just validated it.
148                        if let Err(Error::Integrity(err)) = settings
149                            .edit(data_dir, |_| Some(()))
150                            .expect("Some always returns Some")
151                            .1
152                        {
153                            panic!(
154                                "The identity conversion from a validated settings file must
155                                    always be valid, but we found an integrity error: {:?}",
156                                err
157                            );
158                        }
159                    }
160                    settings
161                },
162                Err(()) => {
163                    // Rename existing file to .invalid.ron
164                    let mut new_path = path.with_extension("invalid.ron");
165
166                    // If invalid path already exists append number
167                    for i in 1.. {
168                        if !new_path.exists() {
169                            break;
170                        }
171
172                        warn!(
173                            ?new_path,
174                            "Path to move invalid settings exists, appending number"
175                        );
176                        new_path = path.with_extension(format!("invalid{}.ron", i));
177                    }
178
179                    warn!("Renaming invalid settings file to: {}", new_path.display());
180                    if let Err(e) = fs::rename(&path, &new_path) {
181                        warn!(?e, ?path, ?new_path, "Failed to rename settings file.");
182                    }
183
184                    create_and_save_default(&path)
185                },
186            }
187        } else {
188            create_and_save_default(&path)
189        }
190    }
191
192    /// If the result of calling `f` is `None`, we return `None` (this
193    /// constitutes an early return and lets us abandon the in-progress
194    /// edit).  For example, this can be used to avoid adding a new ban
195    /// entry if someone is already banned and the user didn't explicitly
196    /// specify that they wanted to add a new ban record, even though it
197    /// would be completely valid to attach one.
198    ///
199    /// Otherwise (the result of calling `f` was `Some(r))`, we always return
200    /// `Some((r, res))`, where:
201    ///
202    /// If `res` is `Ok(())`, validation succeeded for the edited, and changes
203    /// made inside the closure are applied both in memory (to self) and
204    /// atomically on disk.
205    ///
206    /// Otherwise `(res is Err(e))`, some step in the edit process failed.
207    /// Specifically:
208    ///
209    /// * If `e` is `ErrorInternal::Integrity`, validation failed and the
210    ///   settings were not updated.
211    /// * If `e` is `ErrorInternal::Io`, validation succeeded and the settings
212    ///   were updated in memory, but they could not be saved to storage (and a
213    ///   warning was logged). The reason we return an error even though the
214    ///   operation was partially successful is so we can alert the player who
215    ///   ran the command about the failure, as they will often be an
216    ///   administrator who can usefully act upon that information.
217    #[must_use]
218    fn edit<R>(
219        &mut self,
220        data_dir: &Path,
221        f: impl FnOnce(&mut Self) -> Option<R>,
222    ) -> Option<(R, Result<(), Error<Self>>)> {
223        let path = Self::get_path(data_dir);
224
225        // First, edit a copy.
226        let mut copy = self.clone();
227        let r = f(&mut copy)?;
228        // Validate integrity of the raw data before saving (by making sure that
229        // converting to and from the Settings format still produces a valid
230        // file).
231        Some((r, match save_to_file(copy, &path) {
232            Ok(new_settings) => {
233                *self = new_settings;
234                Ok(())
235            },
236            Err(ErrorInternal::Io(err, new_settings)) => {
237                warn!("Failed to save setting: {:?}", err);
238                *self = new_settings;
239                Err(Error::Io(err))
240            },
241            Err(ErrorInternal::Integrity(err)) => Err(Error::Integrity(err)),
242        }))
243    }
244
245    fn get_path(data_dir: &Path) -> PathBuf {
246        let mut path = super::with_config_dir(data_dir);
247        path.push(Self::FILENAME);
248        path
249    }
250}
251
252fn save_to_file<S: EditableSetting>(setting: S, path: &Path) -> Result<S, ErrorInternal<S>> {
253    let raw: <S as EditableSetting>::Setting = setting.into();
254    let ron = ron::ser::to_string_pretty(&raw, ron::ser::PrettyConfig::default())
255        .expect("RON does not throw any parse errors during serialization to string.");
256    // This has the side effect of validating the copy, meaning it's safe to save
257    // the file.
258    let (_, settings): (Version, S) = raw.try_into().map_err(ErrorInternal::Integrity)?;
259    // Create dir if it doesn't exist
260    if let Some(dir) = path.parent()
261        && let Err(err) = fs::create_dir_all(dir)
262    {
263        return Err(ErrorInternal::Io(err, settings));
264    }
265    // Atomically write the validated string to the settings file.
266    let atomic_file = AtomicFile::new(path, OverwriteBehavior::AllowOverwrite);
267    match atomic_file.write(|file| file.write_all(ron.as_bytes())) {
268        Ok(()) => Ok(settings),
269        Err(AtomicError::Internal(err)) | Err(AtomicError::User(err)) => {
270            Err(ErrorInternal::Io(err, settings))
271        },
272    }
273}
274
275fn create_and_save_default<S: EditableSetting>(path: &Path) -> S {
276    let default = S::default();
277    match save_to_file(default, path) {
278        Ok(settings) => settings,
279        Err(ErrorInternal::Io(e, settings)) => {
280            error!(?e, "Failed to create default setting file!");
281            settings
282        },
283        Err(ErrorInternal::Integrity(err)) => {
284            panic!(
285                "The default settings file must always be valid, but we found an integrity error: \
286                 {:?}",
287                err
288            );
289        },
290    }
291}