metadata_backup/
metadata.rs

1// Copyright 2019 metadata-backup Authors (see AUTHORS.md)
2
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6
7//     http://www.apache.org/licenses/LICENSE-2.0
8
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::os::unix::fs::MetadataExt;
16use std::os::unix::fs::PermissionsExt;
17use std::path::Path;
18use std::time::SystemTime;
19
20use serde::{Deserialize, Serialize};
21
22#[derive(Clone, Deserialize, Serialize, Debug, PartialEq)]
23pub struct Metadata {
24    pub name: String,
25    pub size: u64,
26    pub is_dir: bool,
27    #[serde(with = "format_datetimes")]
28    pub atime: Option<SystemTime>,
29    #[serde(with = "format_datetimes")]
30    pub mtime: Option<SystemTime>,
31    #[serde(with = "format_datetimes")]
32    pub ctime: Option<SystemTime>,
33    pub st_mode: Option<u32>,
34    pub st_mode_string: Option<String>,
35    pub uid: Option<u32>,
36    pub gid: Option<u32>,
37    #[serde(deserialize_with = "deserialize_link::deserialize")]
38    pub link: Option<String>,
39}
40
41impl Metadata {
42    pub fn new<P: AsRef<Path>>(p: P) -> Result<Metadata, std::io::Error> {
43        let fpath: &Path = p.as_ref();
44        let name = fpath.file_name().unwrap().to_str().unwrap();
45        let meta = fpath.symlink_metadata()?;
46        let file_type = meta.file_type();
47        let mode = if cfg!(target_os = "linux") {
48            Some(meta.permissions().mode())
49        } else {
50            None
51        };
52
53        let (uid, gid): (Option<u32>, Option<u32>) = if cfg!(target_os = "linux") {
54            (Some(meta.uid()), Some(meta.gid()))
55        } else {
56            (None, None)
57        };
58
59        let link: Option<String> = if file_type.is_symlink() {
60            fpath.read_link()?.to_str().map(String::from)
61        } else {
62            None
63        };
64
65        Ok(Metadata {
66            name: name.to_string(),
67            size: meta.len(),
68            is_dir: file_type.is_dir(),
69            atime: meta.accessed().ok(),
70            mtime: meta.modified().ok(),
71            ctime: meta.created().ok(),
72            st_mode: mode,
73            st_mode_string: mode.map(strmode::strmode),
74            uid: uid,
75            gid: gid,
76            link: link,
77        })
78    }
79}
80
81mod format_datetimes {
82    use chrono::{DateTime, SecondsFormat, TimeZone, Utc};
83    use serde::{self, Deserialize, Deserializer, Serializer};
84    use std::time::SystemTime;
85
86    pub fn serialize<S>(system_time: &Option<SystemTime>, serializer: S) -> Result<S::Ok, S::Error>
87    where
88        S: Serializer,
89    {
90        let timestamp = match system_time {
91            Some(ts) => ts
92                .duration_since(SystemTime::UNIX_EPOCH)
93                .map_err(serde::ser::Error::custom)?
94                .as_secs(),
95            None => {
96                return serializer.serialize_str("");
97            }
98        };
99
100        let date_time = Utc.timestamp(timestamp as i64, 0);
101        let dt_str = date_time.to_rfc3339_opts(SecondsFormat::Secs, true);
102        serializer.serialize_str(&dt_str)
103    }
104
105    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<SystemTime>, D::Error>
106    where
107        D: Deserializer<'de>,
108    {
109        let s = String::deserialize(deserializer)?;
110
111        if s == "" {
112            Ok(None)
113        } else {
114            let dt = DateTime::parse_from_rfc3339(&s).map_err(serde::de::Error::custom)?;
115            let ts = dt.timestamp();
116            let st = SystemTime::UNIX_EPOCH.checked_add(std::time::Duration::from_secs(ts as u64));
117
118            st.ok_or(serde::de::Error::custom(
119                "Could not deserialize invalid timestamp.",
120            ))
121            .map(Some)
122        }
123    }
124}
125
126/// The `link` field must either be `None` or a canonical full path; in Rust, it
127/// is Option<String>, but some serialization formats do not have a specific null
128/// string. We could serialize `link` as two fields (`is_link` and `link`), but
129/// since `link` can never be `Some("")`, we can just deserialize an empty
130/// string as `None` and all other values as `Some(the_value)`.
131mod deserialize_link {
132    use serde::{self, Deserialize, Deserializer};
133    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
134    where
135        D: Deserializer<'de>,
136    {
137        let s = String::deserialize(deserializer)?;
138
139        if s == "" {
140            Ok(None)
141        } else {
142            Ok(Some(s))
143        }
144    }
145}