Auto merge of #30872 - pitdicker:expand_open_options, r=alexcrichton

Tracking issue: #30014

This implements the RFC and makes a few other changes.
I have added a few extra tests, and made the Windows and
Unix code as similar as possible.

Part of the RFC mentions the unstable OpenOptionsExt trait
on Windows (see #27720). I have added a few extra methods
to future-proof it for CreateFile2.
This commit is contained in:
bors
2016-01-20 17:19:21 +00:00
6 changed files with 471 additions and 168 deletions

View File

@@ -93,16 +93,30 @@ pub const CREATE_NEW: DWORD = 1;
pub const OPEN_ALWAYS: DWORD = 4;
pub const OPEN_EXISTING: DWORD = 3;
pub const TRUNCATE_EXISTING: DWORD = 5;
pub const FILE_APPEND_DATA: DWORD = 0x00000004;
pub const FILE_READ_DATA: DWORD = 0x00000001;
pub const FILE_WRITE_DATA: DWORD = 0x00000002;
pub const STANDARD_RIGHTS_READ: DWORD = 0x20000;
pub const STANDARD_RIGHTS_WRITE: DWORD = 0x20000;
pub const FILE_WRITE_EA: DWORD = 0x00000010;
pub const FILE_APPEND_DATA: DWORD = 0x00000004;
pub const FILE_READ_EA: DWORD = 0x00000008;
pub const SYNCHRONIZE: DWORD = 0x00100000;
pub const FILE_WRITE_ATTRIBUTES: DWORD = 0x00000100;
pub const FILE_WRITE_EA: DWORD = 0x00000010;
pub const FILE_EXECUTE: DWORD = 0x00000020;
pub const FILE_READ_ATTRIBUTES: DWORD = 0x00000080;
pub const FILE_WRITE_ATTRIBUTES: DWORD = 0x00000100;
pub const DELETE: DWORD = 0x00008000;
pub const READ_CONTROL: DWORD = 0x00020000;
pub const WRITE_DAC: DWORD = 0x00040000;
pub const WRITE_OWNER: DWORD = 0x00080000;
pub const SYNCHRONIZE: DWORD = 0x00100000;
pub const GENERIC_READ: DWORD = 0x80000000;
pub const GENERIC_WRITE: DWORD = 0x40000000;
pub const GENERIC_EXECUTE: DWORD = 0x20000000;
pub const GENERIC_ALL: DWORD = 0x10000000;
pub const STANDARD_RIGHTS_READ: DWORD = READ_CONTROL;
pub const STANDARD_RIGHTS_WRITE: DWORD = READ_CONTROL;
pub const STANDARD_RIGHTS_EXECUTE: DWORD = READ_CONTROL;
pub const FILE_GENERIC_READ: DWORD = STANDARD_RIGHTS_READ | FILE_READ_DATA |
FILE_READ_ATTRIBUTES |
FILE_READ_EA |
@@ -113,6 +127,14 @@ pub const FILE_GENERIC_WRITE: DWORD = STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA |
FILE_APPEND_DATA |
SYNCHRONIZE;
pub const SECURITY_ANONYMOUS: DWORD = 0 << 16;
pub const SECURITY_IDENTIFICATION: DWORD = 1 << 16;
pub const SECURITY_IMPERSONATION: DWORD = 2 << 16;
pub const SECURITY_DELEGATION: DWORD = 3 << 16;
pub const SECURITY_CONTEXT_TRACKING: DWORD = 0x00040000;
pub const SECURITY_EFFECTIVE_ONLY: DWORD = 0x00080000;
pub const SECURITY_SQOS_PRESENT: DWORD = 0x00100000;
#[repr(C)]
#[derive(Copy)]
pub struct WIN32_FIND_DATAW {

View File

@@ -25,45 +25,131 @@ use sys_common::{AsInnerMut, AsInner};
pub trait OpenOptionsExt {
/// Overrides the `dwDesiredAccess` argument to the call to `CreateFile`
/// with the specified value.
fn desired_access(&mut self, access: u32) -> &mut Self;
/// Overrides the `dwCreationDisposition` argument to the call to
/// `CreateFile` with the specified value.
///
/// This will override any values of the standard `create` flags, for
/// example.
fn creation_disposition(&mut self, val: u32) -> &mut Self;
/// Overrides the `dwFlagsAndAttributes` argument to the call to
/// `CreateFile` with the specified value.
/// This will override the `read`, `write`, and `append` flags on the
/// `OpenOptions` structure. This method provides fine-grained control over
/// the permissions to read, write and append data, attributes (like hidden
/// and system) and extended attributes.
///
/// This will override any values of the standard flags on the
/// `OpenOptions` structure.
fn flags_and_attributes(&mut self, val: u32) -> &mut Self;
/// # Examples
///
/// ```no_run
/// #![feature(open_options_ext)]
/// use std::fs::OpenOptions;
/// use std::os::windows::fs::OpenOptionsExt;
///
/// // Open without read and write permission, for example if you only need
/// // to call `stat()` on the file
/// let file = OpenOptions::new().access_mode(0).open("foo.txt");
/// ```
fn access_mode(&mut self, access: u32) -> &mut Self;
/// Overrides the `dwShareMode` argument to the call to `CreateFile` with
/// the specified value.
///
/// This will override any values of the standard flags on the
/// `OpenOptions` structure.
/// By default `share_mode` is set to
/// `FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE`. Specifying
/// less permissions denies others to read from, write to and/or delete the
/// file while it is open.
///
/// # Examples
///
/// ```no_run
/// #![feature(open_options_ext)]
/// use std::fs::OpenOptions;
/// use std::os::windows::fs::OpenOptionsExt;
///
/// // Do not allow others to read or modify this file while we have it open
/// // for writing
/// let file = OpenOptions::new().write(true)
/// .share_mode(0)
/// .open("foo.txt");
/// ```
fn share_mode(&mut self, val: u32) -> &mut Self;
/// Sets extra flags for the `dwFileFlags` argument to the call to
/// `CreateFile2` (or combines it with `attributes` and `security_qos_flags`
/// to set the `dwFlagsAndAttributes` for `CreateFile`).
///
/// Custom flags can only set flags, not remove flags set by Rusts options.
/// This options overwrites any previously set custom flags.
///
/// # Examples
///
/// ```rust,ignore
/// extern crate winapi;
/// use std::fs::OpenOptions;
/// use std::os::windows::fs::OpenOptionsExt;
///
/// let mut options = OpenOptions::new();
/// options.create(true).write(true);
/// if cfg!(windows) {
/// options.custom_flags(winapi::FILE_FLAG_DELETE_ON_CLOSE);
/// }
/// let file = options.open("foo.txt");
/// ```
#[unstable(feature = "expand_open_options",
reason = "recently added",
issue = "30014")]
fn custom_flags(&mut self, flags: u32) -> &mut Self;
/// Sets the `dwFileAttributes` argument to the call to `CreateFile2` to
/// the specified value (or combines it with `custom_flags` and
/// `security_qos_flags` to set the `dwFlagsAndAttributes` for
/// `CreateFile`).
///
/// If a _new_ file is created because it does not yet exist and
///`.create(true)` or `.create_new(true)` are specified, the new file is
/// given the attributes declared with `.attributes()`.
///
/// If an _existing_ file is opened with `.create(true).truncate(true)`, its
/// existing attributes are preserved and combined with the ones declared
/// with `.attributes()`.
///
/// In all other cases the attributes get ignored.
///
/// # Examples
///
/// ```rust,ignore
/// #![feature(open_options_ext)]
/// extern crate winapi;
/// use std::fs::OpenOptions;
/// use std::os::windows::fs::OpenOptionsExt;
///
/// let file = OpenOptions::new().write(true).create(true)
/// .attributes(winapi::FILE_ATTRIBUTE_HIDDEN)
/// .open("foo.txt");
/// ```
fn attributes(&mut self, val: u32) -> &mut Self;
/// Sets the `dwSecurityQosFlags` argument to the call to `CreateFile2` to
/// the specified value (or combines it with `custom_flags` and `attributes`
/// to set the `dwFlagsAndAttributes` for `CreateFile`).
fn security_qos_flags(&mut self, flags: u32) -> &mut OpenOptions;
}
#[unstable(feature = "open_options_ext",
reason = "may require more thought/methods",
issue = "27720")]
impl OpenOptionsExt for OpenOptions {
fn desired_access(&mut self, access: u32) -> &mut OpenOptions {
self.as_inner_mut().desired_access(access); self
fn access_mode(&mut self, access: u32) -> &mut OpenOptions {
self.as_inner_mut().access_mode(access); self
}
fn creation_disposition(&mut self, access: u32) -> &mut OpenOptions {
self.as_inner_mut().creation_disposition(access); self
fn share_mode(&mut self, share: u32) -> &mut OpenOptions {
self.as_inner_mut().share_mode(share); self
}
fn flags_and_attributes(&mut self, access: u32) -> &mut OpenOptions {
self.as_inner_mut().flags_and_attributes(access); self
fn custom_flags(&mut self, flags: u32) -> &mut OpenOptions {
self.as_inner_mut().custom_flags(flags); self
}
fn share_mode(&mut self, access: u32) -> &mut OpenOptions {
self.as_inner_mut().share_mode(access); self
fn attributes(&mut self, attributes: u32) -> &mut OpenOptions {
self.as_inner_mut().attributes(attributes); self
}
fn security_qos_flags(&mut self, flags: u32) -> &mut OpenOptions {
self.as_inner_mut().security_qos_flags(flags); self
}
}

View File

@@ -54,18 +54,22 @@ pub struct DirEntry {
data: c::WIN32_FIND_DATAW,
}
#[derive(Clone, Default)]
#[derive(Clone)]
pub struct OpenOptions {
create: bool,
append: bool,
// generic
read: bool,
write: bool,
append: bool,
truncate: bool,
desired_access: Option<c::DWORD>,
share_mode: Option<c::DWORD>,
creation_disposition: Option<c::DWORD>,
flags_and_attributes: Option<c::DWORD>,
security_attributes: usize, // *mut T doesn't have a Default impl
create: bool,
create_new: bool,
// system-specific
custom_flags: u32,
access_mode: Option<c::DWORD>,
attributes: c::DWORD,
share_mode: c::DWORD,
security_qos_flags: c::DWORD,
security_attributes: usize, // FIXME: should be a reference
}
#[derive(Clone, PartialEq, Eq, Debug)]
@@ -151,68 +155,86 @@ impl DirEntry {
}
impl OpenOptions {
pub fn new() -> OpenOptions { Default::default() }
pub fn new() -> OpenOptions {
OpenOptions {
// generic
read: false,
write: false,
append: false,
truncate: false,
create: false,
create_new: false,
// system-specific
custom_flags: 0,
access_mode: None,
share_mode: c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE,
attributes: 0,
security_qos_flags: 0,
security_attributes: 0,
}
}
pub fn read(&mut self, read: bool) { self.read = read; }
pub fn write(&mut self, write: bool) { self.write = write; }
pub fn append(&mut self, append: bool) { self.append = append; }
pub fn create(&mut self, create: bool) { self.create = create; }
pub fn truncate(&mut self, truncate: bool) { self.truncate = truncate; }
pub fn creation_disposition(&mut self, val: u32) {
self.creation_disposition = Some(val);
}
pub fn flags_and_attributes(&mut self, val: u32) {
self.flags_and_attributes = Some(val);
}
pub fn desired_access(&mut self, val: u32) {
self.desired_access = Some(val);
}
pub fn share_mode(&mut self, val: u32) {
self.share_mode = Some(val);
}
pub fn create(&mut self, create: bool) { self.create = create; }
pub fn create_new(&mut self, create_new: bool) { self.create_new = create_new; }
pub fn custom_flags(&mut self, flags: u32) { self.custom_flags = flags; }
pub fn access_mode(&mut self, access_mode: u32) { self.access_mode = Some(access_mode); }
pub fn share_mode(&mut self, share_mode: u32) { self.share_mode = share_mode; }
pub fn attributes(&mut self, attrs: u32) { self.attributes = attrs; }
pub fn security_qos_flags(&mut self, flags: u32) { self.security_qos_flags = flags; }
pub fn security_attributes(&mut self, attrs: c::LPSECURITY_ATTRIBUTES) {
self.security_attributes = attrs as usize;
}
fn get_desired_access(&self) -> c::DWORD {
self.desired_access.unwrap_or({
let mut base = if self.read {c::FILE_GENERIC_READ} else {0} |
if self.write {c::FILE_GENERIC_WRITE} else {0};
if self.append {
base &= !c::FILE_WRITE_DATA;
base |= c::FILE_APPEND_DATA;
}
base
})
fn get_access_mode(&self) -> io::Result<c::DWORD> {
const ERROR_INVALID_PARAMETER: i32 = 87;
match (self.read, self.write, self.append, self.access_mode) {
(_, _, _, Some(mode)) => Ok(mode),
(true, false, false, None) => Ok(c::GENERIC_READ),
(false, true, false, None) => Ok(c::GENERIC_WRITE),
(true, true, false, None) => Ok(c::GENERIC_READ | c::GENERIC_WRITE),
(false, _, true, None) => Ok(c::FILE_GENERIC_WRITE & !c::FILE_WRITE_DATA),
(true, _, true, None) => Ok(c::GENERIC_READ |
(c::FILE_GENERIC_WRITE & !c::FILE_WRITE_DATA)),
(false, false, false, None) => Err(Error::from_raw_os_error(ERROR_INVALID_PARAMETER)),
}
}
fn get_share_mode(&self) -> c::DWORD {
// libuv has a good comment about this, but the basic idea is that
// we try to emulate unix semantics by enabling all sharing by
// allowing things such as deleting a file while it's still open.
self.share_mode.unwrap_or(c::FILE_SHARE_READ |
c::FILE_SHARE_WRITE |
c::FILE_SHARE_DELETE)
}
fn get_creation_mode(&self) -> io::Result<c::DWORD> {
const ERROR_INVALID_PARAMETER: i32 = 87;
fn get_creation_disposition(&self) -> c::DWORD {
self.creation_disposition.unwrap_or({
match (self.create, self.truncate) {
(true, true) => c::CREATE_ALWAYS,
(true, false) => c::OPEN_ALWAYS,
(false, false) => c::OPEN_EXISTING,
(false, true) => {
if self.write && !self.append {
c::CREATE_ALWAYS
} else {
c::TRUNCATE_EXISTING
}
}
}
})
match (self.write, self.append) {
(true, false) => {}
(false, false) =>
if self.truncate || self.create || self.create_new {
return Err(Error::from_raw_os_error(ERROR_INVALID_PARAMETER));
},
(_, true) =>
if self.truncate && !self.create_new {
return Err(Error::from_raw_os_error(ERROR_INVALID_PARAMETER));
},
}
Ok(match (self.create, self.truncate, self.create_new) {
(false, false, false) => c::OPEN_EXISTING,
(true, false, false) => c::OPEN_ALWAYS,
(false, true, false) => c::TRUNCATE_EXISTING,
(true, true, false) => c::CREATE_ALWAYS,
(_, _, true) => c::CREATE_NEW,
})
}
fn get_flags_and_attributes(&self) -> c::DWORD {
self.flags_and_attributes.unwrap_or(c::FILE_ATTRIBUTE_NORMAL)
self.custom_flags |
self.attributes |
self.security_qos_flags |
if self.security_qos_flags != 0 { c::SECURITY_SQOS_PRESENT } else { 0 } |
if self.create_new { c::FILE_FLAG_OPEN_REPARSE_POINT } else { 0 }
}
}
@@ -221,8 +243,8 @@ impl File {
let mut opts = OpenOptions::new();
opts.read(!write);
opts.write(write);
opts.flags_and_attributes(c::FILE_FLAG_OPEN_REPARSE_POINT |
c::FILE_FLAG_BACKUP_SEMANTICS);
opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT |
c::FILE_FLAG_BACKUP_SEMANTICS);
File::open(path, &opts)
}
@@ -230,10 +252,10 @@ impl File {
let path = try!(to_u16s(path));
let handle = unsafe {
c::CreateFileW(path.as_ptr(),
opts.get_desired_access(),
opts.get_share_mode(),
try!(opts.get_access_mode()),
opts.share_mode,
opts.security_attributes as *mut _,
opts.get_creation_disposition(),
try!(opts.get_creation_mode()),
opts.get_flags_and_attributes(),
ptr::null_mut())
};
@@ -533,7 +555,10 @@ pub fn stat(p: &Path) -> io::Result<FileAttr> {
// metadata information is.
if attr.is_reparse_point() {
let mut opts = OpenOptions::new();
opts.flags_and_attributes(c::FILE_FLAG_BACKUP_SEMANTICS);
// No read or write permissions are necessary
opts.access_mode(0);
// This flag is so we can open directories too
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);
let file = try!(File::open(p, &opts));
file.file_attr()
} else {
@@ -577,9 +602,10 @@ fn get_path(f: &File) -> io::Result<PathBuf> {
pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
let mut opts = OpenOptions::new();
opts.read(true);
// No read or write permissions are necessary
opts.access_mode(0);
// This flag is so we can open directories too
opts.flags_and_attributes(c::FILE_FLAG_BACKUP_SEMANTICS);
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);
let f = try!(File::open(p, &opts));
get_path(&f)
}