1 // Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! Blocking Windows-based file I/O
14 use libc::{self, c_int};
18 use sys::os::fill_utf16_buf_and_decode;
27 use sys_common::{keep_going, eof, mkerr_libc};
29 use io::{FilePermission, Write, UnstableFileStat, Open, FileAccess, FileMode};
30 use io::{IoResult, IoError, FileStat, SeekStyle};
31 use io::{Read, Truncate, SeekCur, SeekSet, ReadWrite, SeekEnd, Append};
33 pub type fd_t = libc::c_int;
36 /// The underlying C file descriptor.
39 /// Whether to close the file descriptor on drop.
44 pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc {
45 FileDesc { fd: fd, close_on_drop: close_on_drop }
48 pub fn read(&self, buf: &mut [u8]) -> IoResult<uint> {
51 libc::ReadFile(self.handle(), buf.as_ptr() as libc::LPVOID,
52 buf.len() as libc::DWORD, &mut read,
58 Err(super::last_error())
62 pub fn write(&self, buf: &[u8]) -> IoResult<()> {
63 let mut cur = buf.as_ptr();
64 let mut remaining = buf.len();
68 libc::WriteFile(self.handle(), cur as libc::LPVOID,
69 remaining as libc::DWORD, &mut amt,
73 remaining -= amt as uint;
74 cur = unsafe { cur.offset(amt as int) };
76 return Err(super::last_error())
82 pub fn fd(&self) -> fd_t { self.fd }
84 pub fn handle(&self) -> libc::HANDLE {
85 unsafe { libc::get_osfhandle(self.fd()) as libc::HANDLE }
88 // A version of seek that takes &self so that tell can call it
89 // - the private seek should of course take &mut self.
90 fn seek_common(&self, pos: i64, style: SeekStyle) -> IoResult<u64> {
91 let whence = match style {
92 SeekSet => libc::FILE_BEGIN,
93 SeekEnd => libc::FILE_END,
94 SeekCur => libc::FILE_CURRENT,
98 match libc::SetFilePointerEx(self.handle(), pos, &mut newpos, whence) {
99 0 => Err(super::last_error()),
100 _ => Ok(newpos as u64),
105 pub fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<u64> {
106 self.seek_common(pos, style)
109 pub fn tell(&self) -> IoResult<u64> {
110 self.seek_common(0, SeekCur)
113 pub fn fsync(&mut self) -> IoResult<()> {
114 super::mkerr_winbool(unsafe {
115 libc::FlushFileBuffers(self.handle())
119 pub fn datasync(&mut self) -> IoResult<()> { return self.fsync(); }
121 pub fn truncate(&mut self, offset: i64) -> IoResult<()> {
122 let orig_pos = try!(self.tell());
123 let _ = try!(self.seek(offset, SeekSet));
125 match libc::SetEndOfFile(self.handle()) {
126 0 => Err(super::last_error()),
130 let _ = self.seek(orig_pos as i64, SeekSet);
134 pub fn fstat(&self) -> IoResult<io::FileStat> {
135 let mut stat: libc::stat = unsafe { mem::zeroed() };
136 match unsafe { libc::fstat(self.fd(), &mut stat) } {
137 0 => Ok(mkstat(&stat)),
138 _ => Err(super::last_error()),
142 /// Extract the actual filedescriptor without closing it.
143 pub fn unwrap(self) -> fd_t {
145 unsafe { mem::forget(self) };
150 impl Drop for FileDesc {
152 // closing stdio file handles makes no sense, so never do it. Also, note
153 // that errors are ignored when closing a file descriptor. The reason
154 // for this is that if an error occurs we don't actually know if the
155 // file descriptor was closed or not, and if we retried (for something
156 // like EINTR), we might close another valid file descriptor (opened
157 // after we closed ours.
158 if self.close_on_drop && self.fd > libc::STDERR_FILENO {
159 let n = unsafe { libc::close(self.fd) };
161 println!("error {} when closing file descriptor {}", n, self.fd);
167 pub fn to_utf16(s: &Path) -> IoResult<Vec<u16>> {
168 sys::to_utf16(s.as_str())
171 pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult<FileDesc> {
172 // Flags passed to open_osfhandle
173 let flags = match fm {
175 Append => libc::O_APPEND,
176 Truncate => libc::O_TRUNC,
178 let flags = match fa {
179 Read => flags | libc::O_RDONLY,
180 Write => flags | libc::O_WRONLY | libc::O_CREAT,
181 ReadWrite => flags | libc::O_RDWR | libc::O_CREAT,
183 let mut dwDesiredAccess = match fa {
184 Read => libc::FILE_GENERIC_READ,
185 Write => libc::FILE_GENERIC_WRITE,
186 ReadWrite => libc::FILE_GENERIC_READ | libc::FILE_GENERIC_WRITE
189 // libuv has a good comment about this, but the basic idea is what we try to
190 // emulate unix semantics by enabling all sharing by allowing things such as
191 // deleting a file while it's still open.
192 let dwShareMode = libc::FILE_SHARE_READ | libc::FILE_SHARE_WRITE |
193 libc::FILE_SHARE_DELETE;
195 let dwCreationDisposition = match (fm, fa) {
196 (Truncate, Read) => libc::TRUNCATE_EXISTING,
197 (Truncate, _) => libc::CREATE_ALWAYS,
198 (Open, Read) => libc::OPEN_EXISTING,
199 (Open, _) => libc::OPEN_ALWAYS,
201 dwDesiredAccess |= libc::FILE_APPEND_DATA;
205 dwDesiredAccess &= !libc::FILE_WRITE_DATA;
206 dwDesiredAccess |= libc::FILE_APPEND_DATA;
211 let mut dwFlagsAndAttributes = libc::FILE_ATTRIBUTE_NORMAL;
212 // Compat with unix, this allows opening directories (see libuv)
213 dwFlagsAndAttributes |= libc::FILE_FLAG_BACKUP_SEMANTICS;
215 let path = try!(to_utf16(path));
216 let handle = unsafe {
217 libc::CreateFileW(path.as_ptr(),
221 dwCreationDisposition,
222 dwFlagsAndAttributes,
225 if handle == libc::INVALID_HANDLE_VALUE {
226 Err(super::last_error())
229 libc::open_osfhandle(handle as libc::intptr_t, flags)
232 let _ = unsafe { libc::CloseHandle(handle) };
233 Err(super::last_error())
235 Ok(FileDesc::new(fd, true))
240 pub fn mkdir(p: &Path, _mode: uint) -> IoResult<()> {
241 let p = try!(to_utf16(p));
242 super::mkerr_winbool(unsafe {
243 // FIXME: turn mode into something useful? #2623
244 libc::CreateDirectoryW(p.as_ptr(), ptr::null_mut())
248 pub fn readdir(p: &Path) -> IoResult<Vec<Path>> {
249 fn prune(root: &Path, dirs: Vec<Path>) -> Vec<Path> {
250 dirs.into_iter().filter(|path| {
251 path.as_vec() != b"." && path.as_vec() != b".."
252 }).map(|path| root.join(path)).collect()
255 let star = p.join("*");
256 let path = try!(to_utf16(&star));
259 let mut wfd = mem::zeroed();
260 let find_handle = libc::FindFirstFileW(path.as_ptr(), &mut wfd);
261 if find_handle != libc::INVALID_HANDLE_VALUE {
262 let mut paths = vec![];
263 let mut more_files = 1 as libc::BOOL;
264 while more_files != 0 {
266 let filename = os::truncate_utf16_at_nul(&wfd.cFileName);
267 match String::from_utf16(filename) {
268 Ok(filename) => paths.push(Path::new(filename)),
270 assert!(libc::FindClose(find_handle) != 0);
272 kind: io::InvalidInput,
273 desc: "path was not valid UTF-16",
274 detail: Some(format!("path was not valid UTF-16: {}", filename)),
276 }, // FIXME #12056: Convert the UCS-2 to invalid utf-8 instead of erroring
279 more_files = libc::FindNextFileW(find_handle, &mut wfd);
281 assert!(libc::FindClose(find_handle) != 0);
284 Err(super::last_error())
289 pub fn unlink(p: &Path) -> IoResult<()> {
290 fn do_unlink(p_utf16: &Vec<u16>) -> IoResult<()> {
291 super::mkerr_winbool(unsafe { libc::DeleteFileW(p_utf16.as_ptr()) })
294 let p_utf16 = try!(to_utf16(p));
295 let res = do_unlink(&p_utf16);
299 // FIXME: change the code below to use more direct calls
300 // than `stat` and `chmod`, to avoid re-conversion to
303 // On unix, a readonly file can be successfully removed. On windows,
304 // however, it cannot. To keep the two platforms in line with
305 // respect to their behavior, catch this case on windows, attempt to
306 // change it to read-write, and then remove the file.
307 if e.kind == io::PermissionDenied {
308 let stat = match stat(p) {
310 Err(..) => return Err(e),
312 if stat.perm.intersects(io::USER_WRITE) { return Err(e) }
314 match chmod(p, (stat.perm | io::USER_WRITE).bits() as uint) {
315 Ok(()) => do_unlink(&p_utf16),
317 // Try to put it back as we found it
318 let _ = chmod(p, stat.perm.bits() as uint);
329 pub fn rename(old: &Path, new: &Path) -> IoResult<()> {
330 let old = try!(to_utf16(old));
331 let new = try!(to_utf16(new));
332 super::mkerr_winbool(unsafe {
333 libc::MoveFileExW(old.as_ptr(), new.as_ptr(), libc::MOVEFILE_REPLACE_EXISTING)
337 pub fn chmod(p: &Path, mode: uint) -> IoResult<()> {
338 let p = try!(to_utf16(p));
340 libc::wchmod(p.as_ptr(), mode as libc::c_int)
344 pub fn rmdir(p: &Path) -> IoResult<()> {
345 let p = try!(to_utf16(p));
346 mkerr_libc(unsafe { libc::wrmdir(p.as_ptr()) })
349 pub fn chown(_p: &Path, _uid: int, _gid: int) -> IoResult<()> {
350 // libuv has this as a no-op, so seems like this should as well?
354 pub fn readlink(p: &Path) -> IoResult<Path> {
355 // FIXME: I have a feeling that this reads intermediate symlinks as well.
356 use sys::c::compat::kernel32::GetFinalPathNameByHandleW;
357 let p = try!(to_utf16(p));
358 let handle = unsafe {
359 libc::CreateFileW(p.as_ptr(),
361 libc::FILE_SHARE_READ,
364 libc::FILE_ATTRIBUTE_NORMAL,
367 if handle == libc::INVALID_HANDLE_VALUE {
368 return Err(super::last_error())
370 // Specify (sz - 1) because the documentation states that it's the size
371 // without the null pointer
372 let ret = fill_utf16_buf_and_decode(|buf, sz| unsafe {
373 GetFinalPathNameByHandleW(handle,
376 libc::VOLUME_NAME_DOS)
378 let ret = match ret {
379 Some(ref s) if s.starts_with(r"\\?\") => { // "
380 Ok(Path::new(s.slice_from(4)))
382 Some(s) => Ok(Path::new(s)),
383 None => Err(super::last_error()),
385 assert!(unsafe { libc::CloseHandle(handle) } != 0);
389 pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> {
390 use sys::c::compat::kernel32::CreateSymbolicLinkW;
391 let src = try!(to_utf16(src));
392 let dst = try!(to_utf16(dst));
393 super::mkerr_winbool(unsafe {
394 CreateSymbolicLinkW(dst.as_ptr(), src.as_ptr(), 0) as libc::BOOL
398 pub fn link(src: &Path, dst: &Path) -> IoResult<()> {
399 let src = try!(to_utf16(src));
400 let dst = try!(to_utf16(dst));
401 super::mkerr_winbool(unsafe {
402 libc::CreateHardLinkW(dst.as_ptr(), src.as_ptr(), ptr::null_mut())
406 fn mkstat(stat: &libc::stat) -> FileStat {
408 size: stat.st_size as u64,
409 kind: match (stat.st_mode as libc::c_int) & libc::S_IFMT {
410 libc::S_IFREG => io::FileType::RegularFile,
411 libc::S_IFDIR => io::FileType::Directory,
412 libc::S_IFIFO => io::FileType::NamedPipe,
413 libc::S_IFBLK => io::FileType::BlockSpecial,
414 libc::S_IFLNK => io::FileType::Symlink,
415 _ => io::FileType::Unknown,
417 perm: FilePermission::from_bits_truncate(stat.st_mode as u32),
418 created: stat.st_ctime as u64,
419 modified: stat.st_mtime as u64,
420 accessed: stat.st_atime as u64,
421 unstable: UnstableFileStat {
422 device: stat.st_dev as u64,
423 inode: stat.st_ino as u64,
424 rdev: stat.st_rdev as u64,
425 nlink: stat.st_nlink as u64,
426 uid: stat.st_uid as u64,
427 gid: stat.st_gid as u64,
436 pub fn stat(p: &Path) -> IoResult<FileStat> {
437 let mut stat: libc::stat = unsafe { mem::zeroed() };
438 let p = try!(to_utf16(p));
439 match unsafe { libc::wstat(p.as_ptr(), &mut stat) } {
440 0 => Ok(mkstat(&stat)),
441 _ => Err(super::last_error()),
445 // FIXME: move this to platform-specific modules (for now)?
446 pub fn lstat(_p: &Path) -> IoResult<FileStat> {
447 // FIXME: implementation is missing
451 pub fn utime(p: &Path, atime: u64, mtime: u64) -> IoResult<()> {
452 let mut buf = libc::utimbuf {
453 actime: atime as libc::time64_t,
454 modtime: mtime as libc::time64_t,
456 let p = try!(to_utf16(p));
458 libc::wutime(p.as_ptr(), &mut buf)