unctool/
lib.rs

1//! # [unctool](https://github.com/poul1x/unctool)
2//!
3//! A library to convert between Linux and Windows UNC paths.
4//! It can convert local Linux path (mount)
5//! to Windows/Linux UNC and vice versa.
6
7#![cfg_attr(test, allow(dead_code))]
8
9mod mounts;
10mod result;
11pub use crate::result::Error;
12pub use crate::result::Result;
13
14const OS_SEP_WINDOWS: &str = r"\";
15const OS_SEP_LINUX: &str = r"/";
16
17const UNC_PREFIX_WINDOWS: &str = r"\\";
18const UNC_PREFIX_LINUX: &str = r"smb://";
19
20#[derive(Debug, PartialEq)]
21pub enum PathType {
22    Windows,
23    Linux,
24}
25
26fn remote_path_to_intermediate(path: &str) -> Result<String> {
27    if path.starts_with(UNC_PREFIX_WINDOWS) {
28        Ok(path.replace(OS_SEP_WINDOWS, OS_SEP_LINUX))
29    } else if path.starts_with(UNC_PREFIX_LINUX) {
30        Ok(path[UNC_PREFIX_LINUX.len() - 2..].to_owned())
31    } else {
32        Err(Error::InvalidPathFormat)
33    }
34}
35
36fn intermediate_path_to_remote(path: &str, path_type: PathType) -> String {
37    match path_type {
38        PathType::Windows => path.replace(OS_SEP_LINUX, OS_SEP_WINDOWS),
39        PathType::Linux => UNC_PREFIX_LINUX[..UNC_PREFIX_LINUX.len() - 2].to_owned() + path,
40    }
41}
42
43/// Convert between Windows UNC and Linux UNC paths
44///
45/// **Parameters**:
46/// - `path` - source path in UNC format
47/// - `path_type` - destination UNC path type
48///
49/// **Returns**: converted UNC path wrapped with [`crate::Result`]
50///
51/// # Example
52///
53/// ```
54/// let invalid_path = r"\\mynas\some\path";
55/// let res = unctool::convert_unc(invalid_path, unctool::PathType::Linux).unwrap();
56/// assert_eq!(res, "smb://mynas/some/path".to_string());
57/// ```
58///
59/// # Errors
60///
61/// Function will return [`Error::InvalidPathFormat`]`
62/// if provided path is not in UNC format
63///
64/// ```
65/// let invalid_path = "invalid-path";
66/// let res = unctool::convert_unc(invalid_path, unctool::PathType::Linux);
67/// assert_eq!(res, Err(unctool::Error::InvalidPathFormat));
68/// ```
69///
70pub fn convert_unc(path: &str, path_type: PathType) -> Result<String> {
71    let im_path = remote_path_to_intermediate(path)?;
72    Ok(intermediate_path_to_remote(&im_path, path_type))
73}
74
75/// Convert Windows/Linux UNC path to local mounted filesystem path
76///
77/// **Parameters**:
78/// - `path` - path in UNC format
79///
80/// **Returns**: local filesystem path wrapped with [`crate::Result`]
81///
82/// # Example
83///
84/// ```no_run
85/// let res = unctool::local_path(r"\\mynas\some\path").unwrap();
86/// assert_eq!(res, r"/mnt/mynas/some/path");
87/// ```
88///
89/// # Errors
90///
91/// Function will return [`Error::InvalidPathFormat`]
92/// if provided path is not in UNC format
93///
94/// ```
95/// let invalid_path = r"invalid-path";
96/// let err = unctool::local_path(invalid_path).unwrap_err();
97/// assert_eq!(err, unctool::Error::InvalidPathFormat);
98/// ```
99///
100/// Function will return [`Error::LocalPathNotFound`]
101/// if local mountpoint is not found for provided UNC path
102///
103/// ```
104/// let invalid_path2 = r"smb://aaa";
105/// let err = unctool::local_path(invalid_path2).unwrap_err();
106/// assert_eq!(err, unctool::Error::LocalPathNotFound);
107/// ```
108///
109pub fn local_path(path: &str) -> Result<String> {
110    let im_path = remote_path_to_intermediate(path)?;
111    mounts::cifs_local_path(&im_path)
112}
113
114/// Convert local mounted filesystem path to Windows/Linux UNC path
115///
116/// **Parameters**:
117/// - `path` - local filesystem path
118/// - `path_type` - destination UNC path type
119///
120/// **Returns**: UNC path wrapped with [`crate::Result`]
121///
122/// # Example
123///
124/// ```no_run
125/// let path = "/mnt/mynas/some/path";
126/// let res = unctool::remote_path(path, unctool::PathType::Windows).unwrap();
127/// assert_eq!(res, r"\\mynas\some\path");
128/// ```
129///
130/// # Errors
131///
132/// Function will return [`Error::RemotePathNotFound`]
133/// if remote share is not found for this path
134///
135/// ```
136/// let path = r"/mnt/no-such-share";
137/// let res = unctool::remote_path(path, unctool::PathType::Windows);
138/// assert_eq!(res, Err(unctool::Error::RemotePathNotFound));
139/// ```
140///
141pub fn remote_path(path: &str, path_type: PathType) -> Result<String> {
142    let im_path = mounts::cifs_remote_path(path)?;
143    Ok(intermediate_path_to_remote(&im_path, path_type))
144}
145
146/// Get unctool library version
147pub fn version() -> &'static str {
148    env!("CARGO_PKG_VERSION")
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    const REMOTE_PATH_WINDOWS: &str = r"\\mynas\some\path";
156    const REMOTE_PATH_LINUX: &str = r"smb://mynas/some/path";
157    const LOCAL_PATH_LINUX: &str = r"/mnt/mynas/some/path";
158
159    #[test]
160    fn test_convert_unc() {
161        let res = convert_unc(REMOTE_PATH_WINDOWS, PathType::Linux).unwrap();
162        assert_eq!(res, REMOTE_PATH_LINUX);
163
164        let res = convert_unc(REMOTE_PATH_WINDOWS, PathType::Windows).unwrap();
165        assert_eq!(res, REMOTE_PATH_WINDOWS);
166
167        let res = convert_unc(REMOTE_PATH_LINUX, PathType::Windows).unwrap();
168        assert_eq!(res, REMOTE_PATH_WINDOWS);
169
170        let res = convert_unc(REMOTE_PATH_LINUX, PathType::Linux).unwrap();
171        assert_eq!(res, REMOTE_PATH_LINUX);
172
173        let invalid_path = "invalid-path";
174        let err = convert_unc(invalid_path, PathType::Linux).unwrap_err();
175        assert_eq!(err, Error::InvalidPathFormat);
176    }
177
178    #[test]
179    fn test_local_path() {
180        let res = local_path(REMOTE_PATH_WINDOWS).unwrap();
181        assert_eq!(res, LOCAL_PATH_LINUX);
182
183        let res = local_path(REMOTE_PATH_LINUX).unwrap();
184        assert_eq!(res, LOCAL_PATH_LINUX);
185    }
186
187    #[test]
188    fn test_local_path_invalid_data() {
189        let invalid_path = r"no-such-path";
190        let invalid_path2 = r"smb://";
191        let invalid_path3 = r"smb://aaa/bbb/ccc";
192        let invalid_path4 = r"\\";
193        let invalid_path5 = r"\\aaa\bbb\ccc";
194
195        let err = local_path(invalid_path).unwrap_err();
196        assert_eq!(err, Error::InvalidPathFormat);
197
198        let err = local_path(invalid_path2).unwrap_err();
199        assert_eq!(err, Error::LocalPathNotFound);
200
201        let err = local_path(invalid_path3).unwrap_err();
202        assert_eq!(err, Error::LocalPathNotFound);
203
204        let err = local_path(invalid_path4).unwrap_err();
205        assert_eq!(err, Error::LocalPathNotFound);
206
207        let err = local_path(invalid_path5).unwrap_err();
208        assert_eq!(err, Error::LocalPathNotFound);
209    }
210
211    #[test]
212    fn test_remote_path() {
213        let res = remote_path(LOCAL_PATH_LINUX, PathType::Windows).unwrap();
214        assert_eq!(res, REMOTE_PATH_WINDOWS);
215
216        let res = remote_path(LOCAL_PATH_LINUX, PathType::Linux).unwrap();
217        assert_eq!(res, REMOTE_PATH_LINUX);
218
219        let invalid_path = r"no-such-path";
220        let err = remote_path(invalid_path, PathType::Windows).unwrap_err();
221        assert_eq!(err, Error::RemotePathNotFound);
222    }
223}