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#[cfg(test)]
147mod tests {
148    use super::*;
149
150    const REMOTE_PATH_WINDOWS: &str = r"\\mynas\some\path";
151    const REMOTE_PATH_LINUX: &str = r"smb://mynas/some/path";
152    const LOCAL_PATH_LINUX: &str = r"/mnt/mynas/some/path";
153
154    #[test]
155    fn test_convert_unc() {
156        let res = convert_unc(REMOTE_PATH_WINDOWS, PathType::Linux).unwrap();
157        assert_eq!(res, REMOTE_PATH_LINUX);
158
159        let res = convert_unc(REMOTE_PATH_WINDOWS, PathType::Windows).unwrap();
160        assert_eq!(res, REMOTE_PATH_WINDOWS);
161
162        let res = convert_unc(REMOTE_PATH_LINUX, PathType::Windows).unwrap();
163        assert_eq!(res, REMOTE_PATH_WINDOWS);
164
165        let res = convert_unc(REMOTE_PATH_LINUX, PathType::Linux).unwrap();
166        assert_eq!(res, REMOTE_PATH_LINUX);
167
168        let invalid_path = "invalid-path";
169        let err = convert_unc(invalid_path, PathType::Linux).unwrap_err();
170        assert_eq!(err, Error::InvalidPathFormat);
171    }
172
173    #[test]
174    fn test_local_path() {
175        let res = local_path(REMOTE_PATH_WINDOWS).unwrap();
176        assert_eq!(res, LOCAL_PATH_LINUX);
177
178        let res = local_path(REMOTE_PATH_LINUX).unwrap();
179        assert_eq!(res, LOCAL_PATH_LINUX);
180    }
181
182    #[test]
183    fn test_local_path_invalid_data() {
184        let invalid_path = r"no-such-path";
185        let invalid_path2 = r"smb://";
186        let invalid_path3 = r"smb://aaa/bbb/ccc";
187        let invalid_path4 = r"\\";
188        let invalid_path5 = r"\\aaa\bbb\ccc";
189
190        let err = local_path(invalid_path).unwrap_err();
191        assert_eq!(err, Error::InvalidPathFormat);
192
193        let err = local_path(invalid_path2).unwrap_err();
194        assert_eq!(err, Error::LocalPathNotFound);
195
196        let err = local_path(invalid_path3).unwrap_err();
197        assert_eq!(err, Error::LocalPathNotFound);
198
199        let err = local_path(invalid_path4).unwrap_err();
200        assert_eq!(err, Error::LocalPathNotFound);
201
202        let err = local_path(invalid_path5).unwrap_err();
203        assert_eq!(err, Error::LocalPathNotFound);
204    }
205
206    #[test]
207    fn test_remote_path() {
208        let res = remote_path(LOCAL_PATH_LINUX, PathType::Windows).unwrap();
209        assert_eq!(res, REMOTE_PATH_WINDOWS);
210
211        let res = remote_path(LOCAL_PATH_LINUX, PathType::Linux).unwrap();
212        assert_eq!(res, REMOTE_PATH_LINUX);
213
214        let invalid_path = r"no-such-path";
215        let err = remote_path(invalid_path, PathType::Windows).unwrap_err();
216        assert_eq!(err, Error::RemotePathNotFound);
217    }
218}