use crate::error::Error;
use crate::fs::{Attr, FileKind};
pub type Status = i32;
pub const STATUS_SUCCESS: Status = 0;
pub const STATUS_BUFFER_OVERFLOW: Status = 0x8000_0005_u32 as i32;
pub const STATUS_NOT_IMPLEMENTED: Status = 0xC000_0002_u32 as i32;
pub const STATUS_INVALID_PARAMETER: Status = 0xC000_000D_u32 as i32;
pub const STATUS_ACCESS_DENIED: Status = 0xC000_0022_u32 as i32;
pub const STATUS_OBJECT_NAME_INVALID: Status = 0xC000_0033_u32 as i32;
pub const STATUS_OBJECT_NAME_NOT_FOUND: Status = 0xC000_0034_u32 as i32;
pub const STATUS_OBJECT_NAME_COLLISION: Status = 0xC000_0035_u32 as i32;
pub const STATUS_OBJECT_PATH_NOT_FOUND: Status = 0xC000_003A_u32 as i32;
pub const STATUS_DISK_FULL: Status = 0xC000_007F_u32 as i32;
pub const STATUS_MEDIA_WRITE_PROTECTED: Status = 0xC000_00A2_u32 as i32;
pub const STATUS_FILE_IS_A_DIRECTORY: Status = 0xC000_00BA_u32 as i32;
pub const STATUS_NOT_SUPPORTED: Status = 0xC000_00BB_u32 as i32;
pub const STATUS_DIRECTORY_NOT_EMPTY: Status = 0xC000_0101_u32 as i32;
pub const STATUS_FILE_CORRUPT_ERROR: Status = 0xC000_0102_u32 as i32;
pub const STATUS_NOT_A_DIRECTORY: Status = 0xC000_0103_u32 as i32;
pub const STATUS_NAME_TOO_LONG: Status = 0xC000_0106_u32 as i32;
pub const STATUS_SHARING_VIOLATION: Status = 0xC000_0043_u32 as i32;
pub const STATUS_INTERNAL_ERROR: Status = 0xC000_00E5_u32 as i32;
pub const STATUS_IO_DEVICE_ERROR: Status = 0xC000_0185_u32 as i32;
pub const FILE_ATTRIBUTE_READONLY: u32 = 0x1;
pub const FILE_ATTRIBUTE_HIDDEN: u32 = 0x2;
pub const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10;
pub const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x20;
pub const FILE_ATTRIBUTE_NORMAL: u32 = 0x80;
pub const FILE_SUPERSEDE: u32 = 0;
pub const FILE_OPEN: u32 = 1;
pub const FILE_CREATE: u32 = 2;
pub const FILE_OPEN_IF: u32 = 3;
pub const FILE_OVERWRITE: u32 = 4;
pub const FILE_OVERWRITE_IF: u32 = 5;
pub const FILE_DIRECTORY_FILE: u32 = 0x1;
pub const FILE_NON_DIRECTORY_FILE: u32 = 0x40;
pub const FILE_DELETE_ON_CLOSE: u32 = 0x1000;
pub const FILE_CASE_SENSITIVE_SEARCH: u32 = 0x1;
pub const FILE_CASE_PRESERVED_NAMES: u32 = 0x2;
pub const FILE_UNICODE_ON_DISK: u32 = 0x4;
pub const FILE_READ_ONLY_VOLUME: u32 = 0x8_0000;
#[must_use]
pub const fn status(error: &Error) -> Status {
match error {
Error::Io(_) => STATUS_IO_DEVICE_ERROR,
Error::Corrupt(_) => STATUS_FILE_CORRUPT_ERROR,
Error::NotFound => STATUS_OBJECT_NAME_NOT_FOUND,
Error::Exists => STATUS_OBJECT_NAME_COLLISION,
Error::NotDir => STATUS_NOT_A_DIRECTORY,
Error::IsDir => STATUS_FILE_IS_A_DIRECTORY,
Error::NotEmpty => STATUS_DIRECTORY_NOT_EMPTY,
Error::NoSpace => STATUS_DISK_FULL,
Error::InvalidArgument(_) => STATUS_INVALID_PARAMETER,
Error::Unsupported(_) => STATUS_NOT_SUPPORTED,
Error::NameTooLong => STATUS_NAME_TOO_LONG,
Error::ReadOnly => STATUS_MEDIA_WRITE_PROTECTED,
Error::Busy(_) => STATUS_SHARING_VIOLATION,
}
}
pub fn path(windows: &str) -> Result<String, Status> {
if windows.contains(['/', ':']) {
return Err(STATUS_OBJECT_NAME_INVALID);
}
let path = windows.replace('\\', "/");
Ok(if path.starts_with('/') {
path
} else {
format!("/{path}")
})
}
#[must_use]
pub fn attributes(attr: &Attr, name: &str) -> u32 {
let mut attributes = match attr.kind {
FileKind::Dir => FILE_ATTRIBUTE_DIRECTORY,
FileKind::File => FILE_ATTRIBUTE_ARCHIVE,
FileKind::Symlink => FILE_ATTRIBUTE_READONLY,
};
if attr.perm & 0o222 == 0 {
attributes |= FILE_ATTRIBUTE_READONLY;
}
if name.starts_with('.') && name != "." && name != ".." {
attributes |= FILE_ATTRIBUTE_HIDDEN;
}
if attributes == 0 {
FILE_ATTRIBUTE_NORMAL
} else {
attributes
}
}
#[must_use]
pub const fn permissions(perm: u16, attributes: u32) -> u16 {
if attributes & FILE_ATTRIBUTE_READONLY != 0 {
perm & !0o222
} else if perm & 0o222 == 0 {
perm | 0o200
} else {
perm
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Action {
Open,
Truncate,
CreateFile,
CreateDir,
}
pub const fn action(
disposition: u32,
options: u32,
existing: Option<bool>,
) -> Result<Action, Status> {
let wants_dir = options & FILE_DIRECTORY_FILE != 0;
let wants_file = options & FILE_NON_DIRECTORY_FILE != 0;
if disposition > FILE_OVERWRITE_IF || (wants_dir && wants_file) {
return Err(STATUS_INVALID_PARAMETER);
}
match existing {
Some(true) if wants_file => Err(STATUS_FILE_IS_A_DIRECTORY),
Some(false) if wants_dir => Err(STATUS_NOT_A_DIRECTORY),
Some(_) if disposition == FILE_CREATE => Err(STATUS_OBJECT_NAME_COLLISION),
Some(_) if disposition == FILE_OPEN || disposition == FILE_OPEN_IF => Ok(Action::Open),
Some(true) => Err(STATUS_INVALID_PARAMETER),
Some(false) => Ok(Action::Truncate),
None if disposition == FILE_OPEN || disposition == FILE_OVERWRITE => {
Err(STATUS_OBJECT_NAME_NOT_FOUND)
}
None if wants_dir => {
if disposition == FILE_CREATE || disposition == FILE_OPEN_IF {
Ok(Action::CreateDir)
} else {
Err(STATUS_INVALID_PARAMETER)
}
}
None => Ok(Action::CreateFile),
}
}
#[must_use]
pub const fn reports_existing(disposition: u32) -> bool {
matches!(
disposition,
FILE_SUPERSEDE | FILE_OPEN_IF | FILE_OVERWRITE_IF
)
}
#[must_use]
pub const fn create_status(disposition: u32, outcome: Result<bool, Status>) -> Status {
match outcome {
Ok(false) if reports_existing(disposition) => STATUS_OBJECT_NAME_COLLISION,
Ok(_) => STATUS_SUCCESS,
Err(STATUS_OBJECT_NAME_COLLISION) if reports_existing(disposition) => {
STATUS_SHARING_VIOLATION
}
Err(status) => status,
}
}
const UNIX_EPOCH_TICKS: i64 = 116_444_736_000_000_000;
#[must_use]
pub const fn filetime(nanos: i64) -> u64 {
(nanos.div_euclid(100) + UNIX_EPOCH_TICKS).unsigned_abs()
}
#[must_use]
pub const fn set_time(filetime: u64) -> Option<i64> {
let ticks = filetime.cast_signed();
if ticks <= 0 {
None
} else {
Some((ticks - UNIX_EPOCH_TICKS).saturating_mul(100))
}
}
#[must_use]
pub fn serial(name: &str) -> u32 {
name.bytes().fold(0x811c_9dc5, |hash: u32, byte| {
(hash ^ u32::from(byte)).wrapping_mul(0x0100_0193)
})
}
#[cfg(test)]
mod tests {
use super::*;
fn attr(kind: FileKind, perm: u16) -> Attr {
Attr {
ino: 2,
kind,
perm,
nlink: 1,
uid: 0,
gid: 0,
size: 0,
allocated: 0,
atime: 0,
mtime: 0,
ctime: 0,
}
}
#[test]
fn paths_use_forward_slashes() {
assert_eq!(path("\\").unwrap(), "/");
assert_eq!(path("\\a\\b c.txt").unwrap(), "/a/b c.txt");
assert_eq!(path("a").unwrap(), "/a");
assert_eq!(path("\\a/b"), Err(STATUS_OBJECT_NAME_INVALID));
assert_eq!(path("\\a:stream"), Err(STATUS_OBJECT_NAME_INVALID));
}
#[test]
fn errors_map_to_statuses() {
assert_eq!(status(&Error::NotFound), STATUS_OBJECT_NAME_NOT_FOUND);
assert_eq!(status(&Error::NotEmpty), STATUS_DIRECTORY_NOT_EMPTY);
assert_eq!(status(&Error::ReadOnly), STATUS_MEDIA_WRITE_PROTECTED);
assert_eq!(status(&Error::NoSpace), STATUS_DISK_FULL);
assert_eq!(STATUS_OBJECT_NAME_NOT_FOUND, -1_073_741_772);
}
#[test]
fn attributes_follow_kind_permissions_and_dot_names() {
assert_eq!(
attributes(&attr(FileKind::Dir, 0o755), "d"),
FILE_ATTRIBUTE_DIRECTORY
);
assert_eq!(
attributes(&attr(FileKind::File, 0o644), "f"),
FILE_ATTRIBUTE_ARCHIVE
);
assert_eq!(
attributes(&attr(FileKind::File, 0o444), ".f"),
FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN
);
assert_eq!(
attributes(&attr(FileKind::Symlink, 0o777), "l"),
FILE_ATTRIBUTE_READONLY
);
assert_eq!(
attributes(&attr(FileKind::Dir, 0o755), ".."),
FILE_ATTRIBUTE_DIRECTORY
);
}
#[test]
fn the_read_only_attribute_toggles_write_bits() {
assert_eq!(permissions(0o664, FILE_ATTRIBUTE_READONLY), 0o444);
assert_eq!(permissions(0o444, FILE_ATTRIBUTE_NORMAL), 0o644);
assert_eq!(permissions(0o640, FILE_ATTRIBUTE_ARCHIVE), 0o640);
}
#[test]
fn dispositions_follow_create_file() {
let file = Some(false);
let dir = Some(true);
assert_eq!(action(FILE_OPEN, 0, file), Ok(Action::Open));
assert_eq!(action(FILE_OPEN, 0, dir), Ok(Action::Open));
assert_eq!(
action(FILE_OPEN, 0, None),
Err(STATUS_OBJECT_NAME_NOT_FOUND)
);
assert_eq!(
action(FILE_CREATE, 0, file),
Err(STATUS_OBJECT_NAME_COLLISION)
);
assert_eq!(action(FILE_CREATE, 0, None), Ok(Action::CreateFile));
assert_eq!(
action(FILE_CREATE, FILE_DIRECTORY_FILE, None),
Ok(Action::CreateDir)
);
assert_eq!(action(FILE_OPEN_IF, 0, file), Ok(Action::Open));
assert_eq!(action(FILE_OPEN_IF, 0, None), Ok(Action::CreateFile));
assert_eq!(action(FILE_OVERWRITE, 0, file), Ok(Action::Truncate));
assert_eq!(
action(FILE_OVERWRITE, 0, None),
Err(STATUS_OBJECT_NAME_NOT_FOUND)
);
assert_eq!(action(FILE_OVERWRITE_IF, 0, None), Ok(Action::CreateFile));
assert_eq!(action(FILE_SUPERSEDE, 0, file), Ok(Action::Truncate));
assert_eq!(
action(FILE_SUPERSEDE, 0, dir),
Err(STATUS_INVALID_PARAMETER)
);
assert_eq!(
action(FILE_OPEN, FILE_NON_DIRECTORY_FILE, dir),
Err(STATUS_FILE_IS_A_DIRECTORY)
);
assert_eq!(
action(FILE_OPEN, FILE_DIRECTORY_FILE, file),
Err(STATUS_NOT_A_DIRECTORY)
);
assert_eq!(
action(FILE_OVERWRITE_IF, FILE_DIRECTORY_FILE, None),
Err(STATUS_INVALID_PARAMETER)
);
assert_eq!(action(6, 0, None), Err(STATUS_INVALID_PARAMETER));
assert_eq!(
action(
FILE_OPEN,
FILE_DIRECTORY_FILE | FILE_NON_DIRECTORY_FILE,
dir
),
Err(STATUS_INVALID_PARAMETER)
);
}
#[test]
fn create_reports_existing_names_the_way_dokan_expects() {
let collision = STATUS_OBJECT_NAME_COLLISION;
assert_eq!(create_status(FILE_OPEN_IF, Ok(false)), collision);
assert_eq!(create_status(FILE_OVERWRITE_IF, Ok(false)), collision);
assert_eq!(create_status(FILE_SUPERSEDE, Ok(false)), collision);
assert_eq!(create_status(FILE_OPEN_IF, Ok(true)), STATUS_SUCCESS);
assert_eq!(create_status(FILE_OPEN, Ok(false)), STATUS_SUCCESS);
assert_eq!(create_status(FILE_CREATE, Ok(true)), STATUS_SUCCESS);
assert_eq!(
create_status(FILE_OPEN_IF, Err(collision)),
STATUS_SHARING_VIOLATION
);
assert_eq!(create_status(FILE_CREATE, Err(collision)), collision);
assert_eq!(
create_status(FILE_OPEN, Err(STATUS_OBJECT_NAME_NOT_FOUND)),
STATUS_OBJECT_NAME_NOT_FOUND
);
}
#[test]
fn filetimes_round_trip_at_100_ns() {
assert_eq!(filetime(0), 116_444_736_000_000_000);
assert_eq!(filetime(1_700_000_000_000_000_000), 133_444_736_000_000_000);
for nanos in [
0,
100,
1_700_000_000_123_456_700,
-86_400_000_000_000,
i64::MIN / 100 * 100,
i64::MAX / 100 * 100,
] {
assert_eq!(set_time(filetime(nanos)), Some(nanos));
}
assert_eq!(
set_time(filetime(1_700_000_000_000_000_099)),
Some(1_700_000_000_000_000_000)
);
assert_eq!(set_time(0), None);
assert_eq!(set_time(u64::MAX), None);
assert_eq!(set_time(u64::MAX - 1), None);
}
#[test]
fn serial_numbers_follow_the_name() {
assert_eq!(serial(""), 0x811c_9dc5);
assert_ne!(serial("lfs"), serial("lfs2"));
}
}