Skip to content

Commit 01a3028

Browse files
committed
Ensure that Dir::metadata etc. return full metadata values.
Use windows-by-handle functionality when we have it, or do a manual open if we don't.
1 parent 270d26a commit 01a3028

File tree

3 files changed

+106
-8
lines changed

3 files changed

+106
-8
lines changed

cap-primitives/src/windows/fs/metadata_ext.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,25 @@ impl MetadataExt {
6767
///
6868
/// [`std::fs::Metadata::volume_serial_number`]: https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.volume_serial_number
6969
#[inline]
70+
#[allow(unused_mut)]
7071
pub(crate) fn from_just_metadata(std: &fs::Metadata) -> Self {
71-
Self::from_parts(std, None, None, None)
72+
let (mut volume_serial_number, mut number_of_links, mut file_index) = (None, None, None);
73+
74+
#[cfg(windows_by_handle)]
75+
{
76+
use std::os::windows::fs::MetadataExt;
77+
if let Some(some) = std.volume_serial_number() {
78+
volume_serial_number = Some(some);
79+
}
80+
if let Some(some) = std.number_of_links() {
81+
number_of_links = Some(some);
82+
}
83+
if let Some(some) = std.file_index() {
84+
file_index = Some(some);
85+
}
86+
}
87+
88+
Self::from_parts(std, volume_serial_number, number_of_links, file_index)
7289
}
7390

7491
#[inline]
Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,49 @@
1+
#[cfg(windows_by_handle)]
12
use super::get_path::concatenate_or_return_absolute;
23
use crate::fs::{FollowSymlinks, Metadata};
34
use std::{fs, io, path::Path};
5+
use winapi::um::winbase::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT};
6+
#[cfg(not(windows_by_handle))]
7+
use {
8+
crate::fs::{open_unchecked, OpenOptions},
9+
std::os::windows::fs::OpenOptionsExt,
10+
};
411

512
/// *Unsandboxed* function similar to `stat`, but which does not perform sandboxing.
613
pub(crate) fn stat_unchecked(
714
start: &fs::File,
815
path: &Path,
916
follow: FollowSymlinks,
1017
) -> io::Result<Metadata> {
11-
let full_path = concatenate_or_return_absolute(start, path)?;
12-
match follow {
13-
FollowSymlinks::Yes => fs::metadata(full_path),
14-
FollowSymlinks::No => fs::symlink_metadata(full_path),
18+
// When we have `windows_by_handle`, we just call `fs::metadata` etc. and it
19+
// has everything.
20+
#[cfg(windows_by_handle)]
21+
{
22+
let full_path = concatenate_or_return_absolute(start, path)?;
23+
match follow {
24+
FollowSymlinks::Yes => fs::metadata(full_path),
25+
FollowSymlinks::No => fs::symlink_metadata(full_path),
26+
}
27+
.map(Metadata::from_just_metadata)
28+
}
29+
30+
// Otherwise, attempt to open the file to get the metadata that way, as
31+
// that gives us all the info.
32+
#[cfg(not(windows_by_handle))]
33+
{
34+
let mut opts = OpenOptions::new();
35+
opts.access_mode(0);
36+
match follow {
37+
FollowSymlinks::Yes => {
38+
opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS);
39+
opts.follow(FollowSymlinks::Yes);
40+
}
41+
FollowSymlinks::No => {
42+
opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS);
43+
opts.follow(FollowSymlinks::No);
44+
}
45+
}
46+
let file = open_unchecked(start, path, &opts)?;
47+
Metadata::from_file(&file)
1548
}
16-
.map(Metadata::from_just_metadata)
1749
}

tests/metadata-ext.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
#[macro_use]
44
mod sys_common;
55

6-
use cap_fs_ext::MetadataExt;
7-
use sys_common::io::tmpdir;
6+
use cap_fs_ext::{DirExt, MetadataExt};
7+
use sys_common::{io::tmpdir, symlink_supported};
88

99
#[test]
1010
fn test_metadata_ext() {
@@ -14,6 +14,10 @@ fn test_metadata_ext() {
1414
let tmpdir_metadata = check!(tmpdir.dir_metadata());
1515
let a_metadata = check!(a.metadata());
1616
let b_metadata = check!(b.metadata());
17+
let a_dir_metadata = check!(tmpdir.metadata("a"));
18+
let b_dir_metadata = check!(tmpdir.metadata("b"));
19+
let a_symlink_metadata = check!(tmpdir.symlink_metadata("a"));
20+
let b_symlink_metadata = check!(tmpdir.symlink_metadata("b"));
1721

1822
// The directory and files inside it should be on the same device.
1923
assert_eq!(tmpdir_metadata.dev(), a_metadata.dev());
@@ -32,4 +36,49 @@ fn test_metadata_ext() {
3236
check!(tmpdir.hard_link("b", &tmpdir, "c"));
3337
let b_metadata = check!(b.metadata());
3438
assert_eq!(b_metadata.nlink(), 2);
39+
40+
// Check that the metadata has dev/nlink/ino.
41+
tmpdir_metadata.dev();
42+
tmpdir_metadata.nlink();
43+
tmpdir_metadata.ino();
44+
a_metadata.dev();
45+
a_metadata.nlink();
46+
a_metadata.ino();
47+
b_metadata.dev();
48+
b_metadata.nlink();
49+
b_metadata.ino();
50+
a_dir_metadata.dev();
51+
a_dir_metadata.nlink();
52+
a_dir_metadata.ino();
53+
b_dir_metadata.dev();
54+
b_dir_metadata.nlink();
55+
b_dir_metadata.ino();
56+
a_symlink_metadata.dev();
57+
a_symlink_metadata.nlink();
58+
a_symlink_metadata.ino();
59+
b_symlink_metadata.dev();
60+
b_symlink_metadata.nlink();
61+
b_symlink_metadata.ino();
62+
63+
if symlink_supported() {
64+
check!(DirExt::symlink_file(&*tmpdir, "b", "d"));
65+
let d_metadata = check!(tmpdir.metadata("d"));
66+
let d_symlink_metadata = check!(tmpdir.symlink_metadata("d"));
67+
68+
d_metadata.dev();
69+
d_metadata.nlink();
70+
d_metadata.ino();
71+
d_symlink_metadata.dev();
72+
d_symlink_metadata.nlink();
73+
d_symlink_metadata.ino();
74+
75+
assert_ne!(
76+
(d_symlink_metadata.ino(), d_symlink_metadata.dev()),
77+
(b_metadata.ino(), b_metadata.dev())
78+
);
79+
assert_eq!(
80+
(d_metadata.ino(), d_metadata.dev()),
81+
(b_metadata.ino(), b_metadata.dev())
82+
);
83+
}
3584
}

0 commit comments

Comments
 (0)