1use crate::compiler::trim_paths;
2use crate::compiler::{CompileKind, CompileMode, Layout, RustcTargetData};
3use crate::ops;
4use crate::util::HumanBytes;
5use crate::util::data_structures::{HashSet, IndexMap, IndexSet};
6use crate::util::edit_distance;
7use crate::util::errors::CargoResult;
8use crate::util::interning::InternedString;
9use crate::util::{GlobalContext, Progress, ProgressStyle};
10use crate::workspace::profiles::Profiles;
11use crate::workspace::{PackageIdSpec, PackageIdSpecQuery, TargetKind, Workspace};
12use anyhow::bail;
13use cargo_util::paths;
14use cargo_util_terminal::report::Level;
15
16use std::ffi::OsString;
17use std::io::Read;
18use std::path::{Path, PathBuf};
19use std::rc::Rc;
20use std::{fs, io};
21
22pub struct CleanOptions<'gctx> {
23 pub gctx: &'gctx GlobalContext,
24 pub spec: IndexSet<String>,
26 pub targets: Vec<String>,
28 pub profile_specified: bool,
30 pub requested_profile: InternedString,
32 pub doc: bool,
34 pub dry_run: bool,
36 pub explicit_target_dir_arg: bool,
38}
39
40pub struct CleanContext<'gctx> {
41 pub gctx: &'gctx GlobalContext,
42 progress: Box<dyn CleaningProgressBar + 'gctx>,
43 pub dry_run: bool,
44 num_files_removed: u64,
45 num_dirs_removed: u64,
46 total_bytes_removed: u64,
47 seen_file_ids: HashSet<FileId>,
48}
49
50pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
52 let mut target_dir = ws.target_dir();
53 let mut build_dir = ws.build_dir();
54 let gctx = opts.gctx;
55 let mut clean_ctx = CleanContext::new(gctx);
56 clean_ctx.dry_run = opts.dry_run;
57
58 const CLEAN_ABORT_NOTE: &str =
59 "cleaning has been aborted to prevent accidental deletion of unrelated files";
60
61 if let Ok(meta) = fs::symlink_metadata(target_dir.as_path_unlocked()) {
63 if !meta.is_symlink() && !meta.is_dir() {
65 let title = format!("cannot clean `{}`: not a directory", target_dir.display());
66 let report = [Level::ERROR
67 .primary_title(title)
68 .element(Level::NOTE.message(CLEAN_ABORT_NOTE))];
69 gctx.shell().print_report(&report, false)?;
70 return Err(crate::AlreadyPrintedError::new(anyhow::anyhow!("")).into());
71 }
72 }
73
74 if opts.explicit_target_dir_arg {
76 let target_dir_path = target_dir.as_path_unlocked();
77
78 if target_dir_path.exists()
80 && let Err(err) = validate_target_dir_tag(target_dir_path)
81 {
82 let title = format!("cannot clean `{}`: {err}", target_dir_path.display());
84 let report = [Level::ERROR
85 .primary_title(title)
86 .element(Level::NOTE.message(CLEAN_ABORT_NOTE))];
87 gctx.shell().print_report(&report, false)?;
88 return Err(crate::AlreadyPrintedError::new(anyhow::anyhow!("")).into());
89 }
90 }
91
92 if opts.doc {
93 if !opts.spec.is_empty() {
94 bail!("--doc cannot be used with -p");
101 }
102 let doc_dirs = CompileKind::from_requested_targets(gctx, &opts.targets)?
104 .into_iter()
105 .map(|kind| {
106 let target_dir = match kind {
107 CompileKind::Host => target_dir.clone(),
108 CompileKind::Target(target) => target_dir.join(target.short_name()),
109 };
110 target_dir.join("doc").into_path_unlocked()
111 })
112 .collect::<Vec<_>>();
113 clean_ctx.remove_paths(&doc_dirs)?;
114 } else {
115 let profiles = Profiles::new(&ws, opts.requested_profile)?;
116
117 if opts.profile_specified {
118 let dir_name = profiles.get_dir_name();
122 target_dir = target_dir.join(dir_name);
123 build_dir = build_dir.join(dir_name);
124 }
125
126 if opts.spec.is_empty() {
132 let paths: &[PathBuf] = if build_dir != target_dir {
133 &[
134 target_dir.into_path_unlocked(),
135 build_dir.into_path_unlocked(),
136 ]
137 } else {
138 &[target_dir.into_path_unlocked()]
139 };
140 clean_ctx.remove_paths(paths)?;
141 } else {
142 clean_specs(
143 &mut clean_ctx,
144 &ws,
145 &profiles,
146 &opts.targets,
147 &opts.spec,
148 opts.dry_run,
149 )?;
150 }
151 }
152
153 clean_ctx.display_summary()?;
154 Ok(())
155}
156
157fn validate_target_dir_tag(target_dir_path: &Path) -> CargoResult<()> {
158 const TAG_SIGNATURE: &[u8] = b"Signature: 8a477f597d28d172789f06886806bc55";
159
160 let tag_path = target_dir_path.join("CACHEDIR.TAG");
161
162 if tag_path.is_symlink() {
164 bail!("expect `CACHEDIR.TAG` to be a regular file, got a symlink");
165 }
166
167 if !tag_path.is_file() {
168 bail!("missing or invalid `CACHEDIR.TAG` file");
169 }
170
171 let mut file = fs::File::open(&tag_path)
172 .map_err(|err| anyhow::anyhow!("failed to open `{}`: {}", tag_path.display(), err))?;
173
174 let mut buf = [0u8; TAG_SIGNATURE.len()];
175 match file.read_exact(&mut buf) {
176 Ok(()) if &buf[..] == TAG_SIGNATURE => {}
177 Err(e) if e.kind() != io::ErrorKind::UnexpectedEof => {
178 bail!("failed to read `{}`: {e}", tag_path.display());
179 }
180 _ => {
181 bail!("invalid signature in `CACHEDIR.TAG` file");
182 }
183 }
184
185 Ok(())
186}
187
188fn clean_specs(
189 clean_ctx: &mut CleanContext<'_>,
190 ws: &Workspace<'_>,
191 profiles: &Profiles,
192 targets: &[String],
193 spec: &IndexSet<String>,
194 dry_run: bool,
195) -> CargoResult<()> {
196 let requested_kinds = CompileKind::from_requested_targets(clean_ctx.gctx, targets)?;
198 let target_data = RustcTargetData::new(ws, &requested_kinds)?;
199 let (pkg_set, resolve) = ops::resolve_ws(ws, dry_run)?;
200 let prof_dir_name = profiles.get_dir_name();
201 let host_layout = Layout::new(ws, None, &prof_dir_name, true, true)?;
202 let target_layouts: Vec<(CompileKind, Layout)> = requested_kinds
204 .into_iter()
205 .filter_map(|kind| match kind {
206 CompileKind::Target(target) => {
207 match Layout::new(ws, Some(target), &prof_dir_name, true, true) {
208 Ok(layout) => Some(Ok((kind, layout))),
209 Err(e) => Some(Err(e)),
210 }
211 }
212 CompileKind::Host => None,
213 })
214 .collect::<CargoResult<_>>()?;
215 let layouts = if target_layouts.is_empty() {
218 vec![(CompileKind::Host, &host_layout)]
219 } else {
220 target_layouts
221 .iter()
222 .map(|(kind, layout)| (*kind, layout))
223 .collect()
224 };
225 let layouts_with_host: Vec<(CompileKind, &Layout)> =
227 std::iter::once((CompileKind::Host, &host_layout))
228 .chain(layouts.iter().map(|(k, l)| (*k, *l)))
229 .collect();
230
231 let mut pkg_ids = Vec::new();
238 for spec_str in spec.iter() {
239 let spec = PackageIdSpec::parse(spec_str)?;
241 if spec.partial_version().is_some() {
242 clean_ctx.gctx.shell().warn(&format!(
243 "version qualifier in `-p {}` is ignored, \
244 cleaning all versions of `{}` found",
245 spec_str,
246 spec.name()
247 ))?;
248 }
249 if spec.url().is_some() {
250 clean_ctx.gctx.shell().warn(&format!(
251 "url qualifier in `-p {}` ignored, \
252 cleaning all versions of `{}` found",
253 spec_str,
254 spec.name()
255 ))?;
256 }
257 let matches: Vec<_> = resolve.iter().filter(|id| spec.matches(*id)).collect();
258 if matches.is_empty() {
259 let mut suggestion = String::new();
260 suggestion.push_str(&edit_distance::closest_msg(
261 &spec.name(),
262 resolve.iter(),
263 |id| id.name().as_str(),
264 "package",
265 ));
266 anyhow::bail!(
267 "package ID specification `{}` did not match any packages{}",
268 spec,
269 suggestion
270 );
271 }
272 pkg_ids.extend(matches);
273 }
274 let packages = pkg_set.get_many(pkg_ids)?;
275
276 clean_ctx.progress = Box::new(CleaningPackagesBar::new(clean_ctx.gctx, packages.len()));
277 let mut dirs_to_clean = DirectoriesToClean::default();
278
279 if clean_ctx.gctx.cli_unstable().build_dir_new_layout {
280 for pkg in packages {
281 clean_ctx.progress.on_cleaning_package(&pkg.name())?;
282
283 for (_compile_kind, layout) in &layouts_with_host {
285 let dir = layout.build_dir().build_unit(&pkg.name());
286 clean_ctx.rm_rf(&dir)?;
287 }
288
289 for target in pkg.targets() {
291 if target.is_custom_build() {
292 continue;
293 }
294 let crate_name: Rc<str> = target.crate_name().into();
295 for &mode in &[
296 CompileMode::Build,
297 CompileMode::Test,
298 CompileMode::Check { test: false },
299 ] {
300 for (compile_kind, layout) in &layouts {
301 let triple = target_data.short_name(compile_kind);
302 let (file_types, _unsupported) = target_data
303 .info(*compile_kind)
304 .rustc_outputs(mode, target.kind(), triple)?;
305 let artifact_dir = layout
306 .artifact_dir()
307 .expect("artifact-dir was not locked during clean");
308 let uplift_dir = match target.kind() {
309 TargetKind::ExampleBin | TargetKind::ExampleLib(..) => {
310 Some(artifact_dir.examples())
311 }
312 TargetKind::Test | TargetKind::Bench => None,
314 _ => Some(artifact_dir.dest()),
315 };
316 if let Some(uplift_dir) = uplift_dir {
317 for file_type in file_types {
318 let uplifted_filename = file_type.uplift_filename(target);
319
320 let dep_info = Path::new(&uplifted_filename)
322 .with_extension("d")
323 .to_string_lossy()
324 .into_owned();
325
326 let uplifted_path = uplift_dir.join(&uplifted_filename);
327 let unremap = trim_paths::append_unremap_suffix(&uplifted_path);
329 clean_ctx.rm_rf(&unremap)?;
330
331 dirs_to_clean.mark_utf(uplift_dir, |filename| {
332 filename == uplifted_filename || filename == dep_info
333 });
334 }
335 }
336 let path_dash = format!("{}-", crate_name);
337
338 dirs_to_clean.mark_utf(layout.build_dir().incremental(), |filename| {
339 filename.starts_with(&path_dash)
340 });
341 }
342 }
343 }
344 }
345 } else {
346 for pkg in packages {
347 clean_ctx.progress.on_cleaning_package(&pkg.name())?;
348
349 for (_, layout) in &layouts_with_host {
351 dirs_to_clean.mark_utf(layout.build_dir().legacy_fingerprint(), |filename| {
352 let Some((pkg_name, _)) = filename.rsplit_once('-') else {
353 return false;
354 };
355
356 pkg_name == pkg.name().as_str()
357 });
358 }
359
360 for target in pkg.targets() {
361 if target.is_custom_build() {
362 for (_, layout) in &layouts_with_host {
364 dirs_to_clean.mark_utf(layout.build_dir().build(), |filename| {
365 let Some((current_name, _)) = filename.rsplit_once('-') else {
366 return false;
367 };
368
369 current_name == pkg.name().as_str()
370 });
371 }
372 continue;
373 }
374 let crate_name: Rc<str> = target.crate_name().into();
375 let path_dot: &str = &format!("{crate_name}.");
376 let path_dash: &str = &format!("{crate_name}-");
377 for &mode in &[
378 CompileMode::Build,
379 CompileMode::Test,
380 CompileMode::Check { test: false },
381 ] {
382 for (compile_kind, layout) in &layouts {
383 let triple = target_data.short_name(compile_kind);
384 let (file_types, _unsupported) = target_data
385 .info(*compile_kind)
386 .rustc_outputs(mode, target.kind(), triple)?;
387 let artifact_dir = layout
388 .artifact_dir()
389 .expect("artifact-dir was not locked during clean");
390 let (dir, uplift_dir) = match target.kind() {
391 TargetKind::ExampleBin | TargetKind::ExampleLib(..) => {
392 (layout.build_dir().examples(), Some(artifact_dir.examples()))
393 }
394 TargetKind::Test | TargetKind::Bench => {
396 (layout.build_dir().legacy_deps(), None)
397 }
398 _ => (layout.build_dir().legacy_deps(), Some(artifact_dir.dest())),
399 };
400
401 for file_type in file_types {
402 let (prefix, suffix) = file_type.output_prefix_suffix(target);
404 let unhashed_name = file_type.output_filename(target, None);
405 let unhashed_unremap =
407 format!("{unhashed_name}{}", trim_paths::UNREMAP_SUFFIX);
408 dirs_to_clean.mark_utf(&dir, |filename| {
409 (filename.starts_with(&prefix) && filename.ends_with(&suffix))
410 || unhashed_name == filename
411 || unhashed_unremap == filename
412 });
413
414 if let Some(uplift_dir) = uplift_dir {
416 let uplifted_path =
417 uplift_dir.join(file_type.uplift_filename(target));
418 clean_ctx.rm_rf(&uplifted_path)?;
419 let dep_info = uplifted_path.with_extension("d");
421 clean_ctx.rm_rf(&dep_info)?;
422 let unremap = trim_paths::append_unremap_suffix(&uplifted_path);
424 clean_ctx.rm_rf(&unremap)?;
425 }
426 }
427 let unhashed_dep_info = format!("{}.d", crate_name);
428 dirs_to_clean.mark_utf(dir, |filename| filename == unhashed_dep_info);
429
430 dirs_to_clean.mark_utf(dir, |filename| {
431 if filename.starts_with(&path_dash) {
432 filename.ends_with(".d")
435 || filename.ends_with(trim_paths::UNREMAP_SUFFIX)
437 } else if filename.starts_with(&path_dot) {
438 [".o", ".dwo", ".dwp"]
440 .iter()
441 .any(|suffix| filename.ends_with(suffix))
442 } else {
443 false
444 }
445 });
446
447 dirs_to_clean.mark_utf(layout.build_dir().incremental(), |filename| {
449 filename.starts_with(path_dash)
450 });
451 }
452 }
453 }
454 }
455 }
456 clean_ctx.rm_rf_all(dirs_to_clean)?;
457
458 Ok(())
459}
460
461#[derive(Default)]
462struct DirectoriesToClean {
463 dir_contents: IndexMap<PathBuf, IndexSet<OsString>>,
464 to_remove: IndexSet<PathBuf>,
465}
466
467impl DirectoriesToClean {
468 fn mark(&mut self, directory: &Path, mut should_remove_entry: impl FnMut(&OsString) -> bool) {
469 let entry = match self.dir_contents.entry(directory.to_owned()) {
470 indexmap::map::Entry::Occupied(occupied_entry) => occupied_entry.into_mut(),
471 indexmap::map::Entry::Vacant(vacant_entry) => {
472 let Ok(dir_entries) = std::fs::read_dir(directory.to_owned()) else {
473 return;
474 };
475 vacant_entry.insert(
476 dir_entries
477 .into_iter()
478 .flatten()
479 .map(|entry| entry.file_name())
480 .collect::<IndexSet<_>>(),
481 )
482 }
483 };
484
485 entry.retain(|path| {
486 let should_remove = should_remove_entry(path);
487 if should_remove {
488 self.to_remove.insert(directory.join(path));
489 }
490 !should_remove
491 });
492 }
493
494 fn mark_utf(&mut self, directory: &Path, mut should_remove_entry: impl FnMut(&str) -> bool) {
495 self.mark(directory, move |filename| {
496 let Some(as_utf) = filename.to_str() else {
497 return false;
498 };
499 should_remove_entry(as_utf)
500 });
501 }
502}
503
504impl<'gctx> CleanContext<'gctx> {
505 pub fn new(gctx: &'gctx GlobalContext) -> Self {
506 let progress = CleaningFolderBar::new(gctx, 0);
509 CleanContext {
510 gctx,
511 progress: Box::new(progress),
512 dry_run: false,
513 num_files_removed: 0,
514 num_dirs_removed: 0,
515 total_bytes_removed: 0,
516 seen_file_ids: HashSet::default(),
517 }
518 }
519
520 fn rm_rf_all(&mut self, dirs: DirectoriesToClean) -> CargoResult<()> {
521 for path in dirs.to_remove {
522 self.rm_rf(&path)?;
523 }
524 Ok(())
525 }
526
527 pub fn rm_rf(&mut self, path: &Path) -> CargoResult<()> {
528 let meta = match fs::symlink_metadata(path) {
529 Ok(meta) => meta,
530 Err(e) => {
531 if e.kind() != std::io::ErrorKind::NotFound {
532 self.gctx
533 .shell()
534 .warn(&format!("cannot access {}: {e}", path.display()))?;
535 }
536 return Ok(());
537 }
538 };
539
540 if !self.dry_run {
542 self.gctx
543 .shell()
544 .verbose(|shell| shell.status("Removing", path.display()))?;
545 }
546 self.progress.display_now()?;
547
548 let mut rm_file = |path: &Path, meta: Result<std::fs::Metadata, _>| {
549 if let Ok(meta) = meta {
550 self.total_bytes_removed += count_bytes(&mut self.seen_file_ids, path, &meta);
551 }
552 self.num_files_removed += 1;
553 if !self.dry_run {
554 paths::remove_file(path)?;
555 }
556 Ok(())
557 };
558
559 if !meta.is_dir() {
560 return rm_file(path, Ok(meta));
561 }
562
563 for entry in walkdir::WalkDir::new(path).contents_first(true) {
564 let entry = entry?;
565 self.progress.on_clean()?;
566 if self.dry_run {
567 self.gctx
572 .shell()
573 .verbose(|shell| Ok(writeln!(shell.out(), "{}", entry.path().display())?))?;
574 }
575 if entry.file_type().is_dir() {
576 self.num_dirs_removed += 1;
577 if !self.dry_run {
582 paths::remove_dir_all(entry.path())?;
583 }
584 } else {
585 rm_file(entry.path(), entry.metadata())?;
586 }
587 }
588
589 Ok(())
590 }
591
592 pub fn display_summary(&self) -> CargoResult<()> {
593 let status = if self.dry_run { "Summary" } else { "Removed" };
594 let byte_count = if self.total_bytes_removed == 0 {
595 String::new()
596 } else {
597 let bytes = HumanBytes(self.total_bytes_removed);
598 format!(", {bytes:.1} total")
599 };
600 let file_count = match (self.num_files_removed, self.num_dirs_removed) {
606 (0, 0) => format!("0 files"),
607 (0, 1) => format!("1 directory"),
608 (0, 2..) => format!("{} directories", self.num_dirs_removed),
609 (1, _) => format!("1 file"),
610 (2.., _) => format!("{} files", self.num_files_removed),
611 };
612 self.gctx
613 .shell()
614 .status(status, format!("{file_count}{byte_count}"))?;
615 if self.dry_run {
616 self.gctx
617 .shell()
618 .warn("no files deleted due to --dry-run")?;
619 }
620 Ok(())
621 }
622
623 pub fn remove_paths(&mut self, paths: &[PathBuf]) -> CargoResult<()> {
629 let num_paths = paths
630 .iter()
631 .map(|path| walkdir::WalkDir::new(path).into_iter().count())
632 .sum();
633 self.progress = Box::new(CleaningFolderBar::new(self.gctx, num_paths));
634 for path in paths {
635 self.rm_rf(path)?;
636 }
637 Ok(())
638 }
639}
640
641trait CleaningProgressBar {
642 fn display_now(&mut self) -> CargoResult<()>;
643 fn on_clean(&mut self) -> CargoResult<()>;
644 fn on_cleaning_package(&mut self, _package: &str) -> CargoResult<()> {
645 Ok(())
646 }
647}
648
649struct CleaningFolderBar<'gctx> {
650 bar: Progress<'gctx>,
651 max: usize,
652 cur: usize,
653}
654
655impl<'gctx> CleaningFolderBar<'gctx> {
656 fn new(gctx: &'gctx GlobalContext, max: usize) -> Self {
657 Self {
658 bar: Progress::with_style("Cleaning", ProgressStyle::Percentage, gctx),
659 max,
660 cur: 0,
661 }
662 }
663
664 fn cur_progress(&self) -> usize {
665 std::cmp::min(self.cur, self.max)
666 }
667}
668
669impl<'gctx> CleaningProgressBar for CleaningFolderBar<'gctx> {
670 fn display_now(&mut self) -> CargoResult<()> {
671 self.bar.tick_now(self.cur_progress(), self.max, "")
672 }
673
674 fn on_clean(&mut self) -> CargoResult<()> {
675 self.cur += 1;
676 self.bar.tick(self.cur_progress(), self.max, "")
677 }
678}
679
680struct CleaningPackagesBar<'gctx> {
681 bar: Progress<'gctx>,
682 max: usize,
683 cur: usize,
684 num_files_folders_cleaned: usize,
685 package_being_cleaned: String,
686}
687
688impl<'gctx> CleaningPackagesBar<'gctx> {
689 fn new(gctx: &'gctx GlobalContext, max: usize) -> Self {
690 Self {
691 bar: Progress::with_style("Cleaning", ProgressStyle::Ratio, gctx),
692 max,
693 cur: 0,
694 num_files_folders_cleaned: 0,
695 package_being_cleaned: String::new(),
696 }
697 }
698
699 fn cur_progress(&self) -> usize {
700 std::cmp::min(self.cur, self.max)
701 }
702
703 fn format_message(&self) -> String {
704 format!(
705 ": {}, {} files/folders cleaned",
706 self.package_being_cleaned, self.num_files_folders_cleaned
707 )
708 }
709}
710
711impl<'gctx> CleaningProgressBar for CleaningPackagesBar<'gctx> {
712 fn display_now(&mut self) -> CargoResult<()> {
713 self.bar
714 .tick_now(self.cur_progress(), self.max, &self.format_message())
715 }
716
717 fn on_clean(&mut self) -> CargoResult<()> {
718 self.bar
719 .tick(self.cur_progress(), self.max, &self.format_message())?;
720 self.num_files_folders_cleaned += 1;
721 Ok(())
722 }
723
724 fn on_cleaning_package(&mut self, package: &str) -> CargoResult<()> {
725 self.cur += 1;
726 self.package_being_cleaned = String::from(package);
727 self.bar
728 .tick(self.cur_progress(), self.max, &self.format_message())
729 }
730}
731
732fn count_bytes(seen: &mut HashSet<FileId>, path: &Path, meta: &fs::Metadata) -> u64 {
733 if !meta.is_file() {
734 return meta.len();
735 }
736 if meta.is_symlink() {
740 return meta.len();
741 }
742 match FileId::new_with_link_count(meta, path) {
743 Some((id, nlink)) => {
744 if nlink <= 1 || seen.insert(id) {
745 meta.len()
746 } else {
747 0
748 }
749 }
750 None => meta.len(),
751 }
752}
753
754#[derive(PartialEq, Eq, Hash)]
763struct FileId((u64, u64));
764
765impl FileId {
766 #[cfg(unix)]
767 fn new_with_link_count(meta: &fs::Metadata, _path: &Path) -> Option<(Self, u64)> {
768 use std::os::unix::fs::MetadataExt;
769 Some((FileId((meta.dev(), meta.ino())), meta.nlink() as u64))
770 }
771
772 #[cfg(windows)]
773 fn new_with_link_count(_meta: &fs::Metadata, path: &Path) -> Option<(Self, u64)> {
774 use std::os::windows::ffi::OsStrExt;
775 use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE};
776 use windows_sys::Win32::Storage::FileSystem::{
777 BY_HANDLE_FILE_INFORMATION, CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_READ_ATTRIBUTES,
778 FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, GetFileInformationByHandle,
779 OPEN_EXISTING,
780 };
781
782 let mut wide: Vec<u16> = path.as_os_str().encode_wide().collect();
783 wide.push(0);
784
785 let handle = unsafe {
786 CreateFileW(
787 wide.as_ptr(),
788 FILE_READ_ATTRIBUTES,
789 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
790 std::ptr::null(),
791 OPEN_EXISTING,
792 FILE_ATTRIBUTE_NORMAL,
793 std::ptr::null_mut(),
794 )
795 };
796 if handle == INVALID_HANDLE_VALUE {
797 return None;
798 }
799
800 let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
801 let ok = unsafe { GetFileInformationByHandle(handle, &mut info) };
802 unsafe { CloseHandle(handle) };
803
804 if ok == 0 {
805 return None;
806 }
807
808 let file_index = ((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64);
809 Some((
810 FileId((info.dwVolumeSerialNumber as u64, file_index)),
811 info.nNumberOfLinks as u64,
812 ))
813 }
814}