cargo_mobile2/os/windows/
ln.rs

1use crate::util::{
2    ln::{Clobber, Error, ErrorCause, LinkType, TargetStyle},
3    prefix_path,
4};
5use std::{
6    borrow::Cow,
7    fs::{remove_dir_all, remove_file},
8    os::windows::ffi::OsStrExt,
9    path::Path,
10};
11use windows::{
12    core::{self, PCWSTR},
13    Win32::{
14        Foundation::{CloseHandle, ERROR_PRIVILEGE_NOT_HELD, GENERIC_READ},
15        Storage::FileSystem::{
16            CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_DELETE_ON_CLOSE,
17            FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_READ, OPEN_EXISTING,
18        },
19    },
20};
21
22pub fn force_symlink(
23    source: impl AsRef<Path>,
24    target: impl AsRef<Path>,
25    target_style: TargetStyle,
26) -> Result<(), Error> {
27    let (source, target) = (source.as_ref(), target.as_ref());
28    let error = |cause: ErrorCause| {
29        Error::new(
30            LinkType::Symbolic,
31            Clobber::FileOnly,
32            source.to_owned(),
33            target.to_owned(),
34            target_style,
35            cause,
36        )
37    };
38    let target = if target_style == TargetStyle::Directory {
39        let file_name = if let Some(file_name) = source.file_name() {
40            file_name
41        } else {
42            return Err(error(ErrorCause::MissingFileName));
43        };
44        Cow::Owned(target.join(file_name))
45    } else {
46        Cow::Borrowed(target)
47    };
48    let is_directory = target
49        .parent()
50        .map(|parent| prefix_path(parent, source).is_dir())
51        .unwrap_or(false);
52    if is_symlink(&target) {
53        delete_symlink(&target).map_err(|err| error(ErrorCause::IOError(err.into())))?;
54    } else if target.is_file() {
55        remove_file(&target).map_err(|err| error(ErrorCause::IOError(err)))?;
56    } else if target.is_dir() {
57        remove_dir_all(&target).map_err(|err| error(ErrorCause::IOError(err)))?;
58    }
59    let result = if is_directory {
60        std::os::windows::fs::symlink_dir(source, target)
61    } else {
62        std::os::windows::fs::symlink_file(source, target)
63    };
64    result.map_err(|err| {
65        if err.raw_os_error() == Some(ERROR_PRIVILEGE_NOT_HELD.0 as i32) {
66            error(ErrorCause::SymlinkNotAllowed)
67        } else {
68            error(ErrorCause::IOError(err))
69        }
70    })?;
71    Ok(())
72}
73
74pub fn force_symlink_relative(
75    abs_source: impl AsRef<Path>,
76    abs_target: impl AsRef<Path>,
77    target_style: TargetStyle,
78) -> Result<(), Error> {
79    let (abs_source, abs_target) = (abs_source.as_ref(), abs_target.as_ref());
80    let rel_source = crate::util::relativize_path(abs_source, abs_target);
81    if target_style == TargetStyle::Directory && rel_source.file_name().is_none() {
82        if let Some(file_name) = abs_source.file_name() {
83            force_symlink(rel_source, abs_target.join(file_name), TargetStyle::File)
84        } else {
85            Err(Error::new(
86                LinkType::Symbolic,
87                Clobber::FileOnly,
88                rel_source,
89                abs_target.to_owned(),
90                target_style,
91                ErrorCause::MissingFileName,
92            ))
93        }
94    } else {
95        force_symlink(rel_source, abs_target, target_style)
96    }
97}
98
99fn delete_symlink(filename: &Path) -> Result<(), core::Error> {
100    let filename = filename
101        .as_os_str()
102        .encode_wide()
103        .chain([0])
104        .collect::<Vec<_>>();
105
106    if let Ok(handle) = unsafe {
107        CreateFileW(
108            PCWSTR::from_raw(filename.as_ptr()),
109            GENERIC_READ.0,
110            FILE_SHARE_READ,
111            None,
112            OPEN_EXISTING,
113            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_DELETE_ON_CLOSE,
114            None,
115        )
116    } {
117        unsafe { CloseHandle(handle)? };
118        Ok(())
119    } else {
120        Err(core::Error::from_win32())
121    }
122}
123
124fn is_symlink(filename: &Path) -> bool {
125    if let Ok(metadata) = std::fs::symlink_metadata(filename) {
126        metadata.file_type().is_symlink()
127    } else {
128        false
129    }
130}