cargo_mobile2/os/windows/
ln.rs1use 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}