cargo_mobile2/os/windows/
ln.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use crate::util::{
    ln::{Clobber, Error, ErrorCause, LinkType, TargetStyle},
    prefix_path,
};
use std::{
    borrow::Cow,
    fs::{remove_dir_all, remove_file},
    os::windows::ffi::OsStrExt,
    path::Path,
};
use windows::{
    core::{self, PCWSTR},
    Win32::{
        Foundation::{CloseHandle, ERROR_PRIVILEGE_NOT_HELD, GENERIC_READ, HANDLE},
        Storage::FileSystem::{
            CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_DELETE_ON_CLOSE,
            FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_READ, OPEN_EXISTING,
        },
    },
};

pub fn force_symlink(
    source: impl AsRef<Path>,
    target: impl AsRef<Path>,
    target_style: TargetStyle,
) -> Result<(), Error> {
    let (source, target) = (source.as_ref(), target.as_ref());
    let error = |cause: ErrorCause| {
        Error::new(
            LinkType::Symbolic,
            Clobber::FileOnly,
            source.to_owned(),
            target.to_owned(),
            target_style,
            cause,
        )
    };
    let target = if target_style == TargetStyle::Directory {
        let file_name = if let Some(file_name) = source.file_name() {
            file_name
        } else {
            return Err(error(ErrorCause::MissingFileName));
        };
        Cow::Owned(target.join(file_name))
    } else {
        Cow::Borrowed(target)
    };
    let is_directory = target
        .parent()
        .map(|parent| prefix_path(parent, source).is_dir())
        .unwrap_or(false);
    if is_symlink(&target) {
        delete_symlink(&target).map_err(|err| error(ErrorCause::IOError(err.into())))?;
    } else if target.is_file() {
        remove_file(&target).map_err(|err| error(ErrorCause::IOError(err)))?;
    } else if target.is_dir() {
        remove_dir_all(&target).map_err(|err| error(ErrorCause::IOError(err)))?;
    }
    let result = if is_directory {
        std::os::windows::fs::symlink_dir(source, target)
    } else {
        std::os::windows::fs::symlink_file(source, target)
    };
    result.map_err(|err| {
        if err.raw_os_error() == Some(ERROR_PRIVILEGE_NOT_HELD.0 as i32) {
            error(ErrorCause::SymlinkNotAllowed)
        } else {
            error(ErrorCause::IOError(err))
        }
    })?;
    Ok(())
}

pub fn force_symlink_relative(
    abs_source: impl AsRef<Path>,
    abs_target: impl AsRef<Path>,
    target_style: TargetStyle,
) -> Result<(), Error> {
    let (abs_source, abs_target) = (abs_source.as_ref(), abs_target.as_ref());
    let rel_source = crate::util::relativize_path(abs_source, abs_target);
    if target_style == TargetStyle::Directory && rel_source.file_name().is_none() {
        if let Some(file_name) = abs_source.file_name() {
            force_symlink(rel_source, abs_target.join(file_name), TargetStyle::File)
        } else {
            Err(Error::new(
                LinkType::Symbolic,
                Clobber::FileOnly,
                rel_source,
                abs_target.to_owned(),
                target_style,
                ErrorCause::MissingFileName,
            ))
        }
    } else {
        force_symlink(rel_source, abs_target, target_style)
    }
}

fn delete_symlink(filename: &Path) -> Result<(), core::Error> {
    let filename = filename
        .as_os_str()
        .encode_wide()
        .chain([0])
        .collect::<Vec<_>>();

    if let Ok(handle) = unsafe {
        CreateFileW(
            PCWSTR::from_raw(filename.as_ptr()),
            GENERIC_READ.0,
            FILE_SHARE_READ,
            None,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_DELETE_ON_CLOSE,
            HANDLE(std::ptr::null_mut()),
        )
    } {
        unsafe { CloseHandle(handle)? };
        Ok(())
    } else {
        Err(core::Error::from_win32())
    }
}

fn is_symlink(filename: &Path) -> bool {
    if let Ok(metadata) = std::fs::symlink_metadata(filename) {
        metadata.file_type().is_symlink()
    } else {
        false
    }
}