From a9e02c8bd809e3045b82d4c9719e786b076096d9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 25 Feb 2016 21:45:59 +0100 Subject: [PATCH 1/2] Small fixes to setting Windows file permissions On Windows files can have a simple read-only permission flag, and the more complex access rights and access control lists. The read-only flag is one bit of the file attributes. To make sure only the read-only flag is toggled and not the other attributes, read the files attributes and write them back with only this bit changed. By reading and setting the attributes from the handle the file does not have to be opened twice. Also directories can not be read-only, here the flag has a different meaning. So we should no longer report directories as read-only, and also not allow making them read-only. --- src/libstd/fs.rs | 3 +- src/libstd/sys/windows/c.rs | 12 +++++-- src/libstd/sys/windows/fs.rs | 68 ++++++++++++++++++++++++++---------- 3 files changed, 61 insertions(+), 22 deletions(-) diff --git a/src/libstd/fs.rs b/src/libstd/fs.rs index badbba21d55cc..14b916513a711 100644 --- a/src/libstd/fs.rs +++ b/src/libstd/fs.rs @@ -1426,7 +1426,8 @@ impl Iterator for WalkDir { /// # Platform-specific behavior /// /// This function currently corresponds to the `chmod` function on Unix -/// and the `SetFileAttributes` function on Windows. +/// and the `CreateFile`, `GetFileInformationByHandle` and +/// `SetFileInformationByHandle` function on Windows. /// Note that, this [may change in the future][changes]. /// [changes]: ../io/index.html#platform-specific-behavior /// diff --git a/src/libstd/sys/windows/c.rs b/src/libstd/sys/windows/c.rs index 5cbfec01bedaa..f1714ffc48fa1 100644 --- a/src/libstd/sys/windows/c.rs +++ b/src/libstd/sys/windows/c.rs @@ -98,6 +98,7 @@ pub const TRUNCATE_EXISTING: DWORD = 5; pub const FILE_WRITE_DATA: DWORD = 0x00000002; pub const FILE_APPEND_DATA: DWORD = 0x00000004; pub const FILE_WRITE_EA: DWORD = 0x00000010; +pub const FILE_READ_ATTRIBUTES: DWORD = 0x00000080; pub const FILE_WRITE_ATTRIBUTES: DWORD = 0x00000100; pub const READ_CONTROL: DWORD = 0x00020000; pub const SYNCHRONIZE: DWORD = 0x00100000; @@ -364,6 +365,15 @@ pub enum FILE_INFO_BY_HANDLE_CLASS { MaximumFileInfoByHandlesClass } +#[repr(C)] +pub struct FILE_BASIC_INFO { + pub CreationTime: LARGE_INTEGER, + pub LastAccessTime: LARGE_INTEGER, + pub LastWriteTime: LARGE_INTEGER, + pub ChangeTime: LARGE_INTEGER, + pub FileAttributes: DWORD, +} + #[repr(C)] pub struct FILE_END_OF_FILE_INFO { pub EndOfFile: LARGE_INTEGER, @@ -855,8 +865,6 @@ extern "system" { pub fn GetConsoleMode(hConsoleHandle: HANDLE, lpMode: LPDWORD) -> BOOL; pub fn RemoveDirectoryW(lpPathName: LPCWSTR) -> BOOL; - pub fn SetFileAttributesW(lpFileName: LPCWSTR, - dwFileAttributes: DWORD) -> BOOL; pub fn GetFileInformationByHandle(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL; diff --git a/src/libstd/sys/windows/fs.rs b/src/libstd/sys/windows/fs.rs index 95fb1e7c60052..854640b166fd2 100644 --- a/src/libstd/sys/windows/fs.rs +++ b/src/libstd/sys/windows/fs.rs @@ -78,7 +78,7 @@ pub struct OpenOptions { } #[derive(Clone, PartialEq, Eq, Debug)] -pub struct FilePermissions { attrs: c::DWORD } +pub struct FilePermissions { readonly: bool } pub struct DirBuilder; @@ -333,6 +333,40 @@ impl File { Ok(newpos as u64) } + pub fn set_attributes(&self, attr: c::DWORD) -> io::Result<()> { + let mut info = c::FILE_BASIC_INFO { + CreationTime: 0, // do not change + LastAccessTime: 0, // do not change + LastWriteTime: 0, // do not change + ChangeTime: 0, // do not change + FileAttributes: attr, + }; + let size = mem::size_of_val(&info); + try!(cvt(unsafe { + c::SetFileInformationByHandle(self.handle.raw(), + c::FileBasicInfo, + &mut info as *mut _ as *mut _, + size as c::DWORD) + })); + Ok(()) + } + + pub fn set_perm(&self, perm: FilePermissions) -> io::Result<()> { + let attr = try!(self.file_attr()).attributes; + match (perm.readonly, attr & c::FILE_ATTRIBUTE_READONLY != 0) { + (true, false) => + if attr & c::FILE_ATTRIBUTE_DIRECTORY != 0 { + // this matches directories, dir symlinks, and junctions. + Err(io::Error::new(io::ErrorKind::PermissionDenied, + "directories can not be read-only")) + } else { + self.set_attributes(attr | c::FILE_ATTRIBUTE_READONLY) + }, + (false, true) => self.set_attributes(attr & !c::FILE_ATTRIBUTE_READONLY), + _ => Ok(()), + } + } + pub fn duplicate(&self) -> io::Result { Ok(File { handle: try!(self.handle.duplicate(0, true, c::DUPLICATE_SAME_ACCESS)), @@ -422,7 +456,12 @@ impl FileAttr { } pub fn perm(&self) -> FilePermissions { - FilePermissions { attrs: self.attributes } + FilePermissions { + // Only files can be read-only. If the flag is set on a directory this means the + // directory its view is customized by Windows (with a Desktop.ini file) + readonly: self.attributes & c::FILE_ATTRIBUTE_READONLY != 0 && + self.attributes & c::FILE_ATTRIBUTE_DIRECTORY == 0 + } } pub fn attrs(&self) -> u32 { self.attributes as u32 } @@ -465,17 +504,8 @@ fn to_u64(ft: &c::FILETIME) -> u64 { } impl FilePermissions { - pub fn readonly(&self) -> bool { - self.attrs & c::FILE_ATTRIBUTE_READONLY != 0 - } - - pub fn set_readonly(&mut self, readonly: bool) { - if readonly { - self.attrs |= c::FILE_ATTRIBUTE_READONLY; - } else { - self.attrs &= !c::FILE_ATTRIBUTE_READONLY; - } - } + pub fn readonly(&self) -> bool { self.readonly } + pub fn set_readonly(&mut self, readonly: bool) { self.readonly = readonly } } impl FileType { @@ -640,12 +670,12 @@ pub fn lstat(path: &Path) -> io::Result { file.file_attr() } -pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { - let p = try!(to_u16s(p)); - unsafe { - try!(cvt(c::SetFileAttributesW(p.as_ptr(), perm.attrs))); - Ok(()) - } +pub fn set_perm(path: &Path, perm: FilePermissions) -> io::Result<()> { + let mut opts = OpenOptions::new(); + opts.access_mode(c::FILE_READ_ATTRIBUTES | c::FILE_WRITE_ATTRIBUTES); + opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS); + let file = try!(File::open(path, &opts)); + file.set_perm(perm) } fn get_path(f: &File) -> io::Result { From b951513f8b70c16c875164960d52ecd01895579b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 26 Feb 2016 07:29:46 +0100 Subject: [PATCH 2/2] Handle removing read-only from directories --- src/libstd/sys/windows/fs.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/libstd/sys/windows/fs.rs b/src/libstd/sys/windows/fs.rs index 854640b166fd2..3400bec126b4c 100644 --- a/src/libstd/sys/windows/fs.rs +++ b/src/libstd/sys/windows/fs.rs @@ -353,17 +353,23 @@ impl File { pub fn set_perm(&self, perm: FilePermissions) -> io::Result<()> { let attr = try!(self.file_attr()).attributes; - match (perm.readonly, attr & c::FILE_ATTRIBUTE_READONLY != 0) { - (true, false) => - if attr & c::FILE_ATTRIBUTE_DIRECTORY != 0 { - // this matches directories, dir symlinks, and junctions. - Err(io::Error::new(io::ErrorKind::PermissionDenied, - "directories can not be read-only")) - } else { - self.set_attributes(attr | c::FILE_ATTRIBUTE_READONLY) - }, - (false, true) => self.set_attributes(attr & !c::FILE_ATTRIBUTE_READONLY), - _ => Ok(()), + if attr & c::FILE_ATTRIBUTE_DIRECTORY != 0 { + // this matches directories, dir symlinks, and junctions. + if perm.readonly { + Err(io::Error::new(io::ErrorKind::PermissionDenied, + "directories can not be read-only")) + } else { + Ok(()) // no reason to fail, as the result is what is expected. + // (the directory will not be read-only) + } + } else { + if perm.readonly == (attr & c::FILE_ATTRIBUTE_READONLY != 0) { + Ok(()) + } else if perm.readonly { + self.set_attributes(attr | c::FILE_ATTRIBUTE_READONLY) + } else { + self.set_attributes(attr & !c::FILE_ATTRIBUTE_READONLY) + } } }