]> git.lizzy.rs Git - rust.git/blob - src/libnative/io/file_win32.rs
5ace8e347c67ef7d5c4e521f91ec125d5314d908
[rust.git] / src / libnative / io / file_win32.rs
1 // Copyright 2013-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.
4 //
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.
10
11 //! Blocking win32-based file I/O
12
13 use alloc::arc::Arc;
14 use libc::{c_int, c_void};
15 use libc;
16 use std::c_str::CString;
17 use std::mem;
18 use std::os::win32::fill_utf16_buf_and_decode;
19 use std::ptr;
20 use std::rt::rtio;
21 use std::rt::rtio::{IoResult, IoError};
22 use std::str;
23 use std::vec;
24
25 pub type fd_t = libc::c_int;
26
27 struct Inner {
28     fd: fd_t,
29     close_on_drop: bool,
30 }
31
32 pub struct FileDesc {
33     inner: Arc<Inner>
34 }
35
36 impl FileDesc {
37     /// Create a `FileDesc` from an open C file descriptor.
38     ///
39     /// The `FileDesc` will take ownership of the specified file descriptor and
40     /// close it upon destruction if the `close_on_drop` flag is true, otherwise
41     /// it will not close the file descriptor when this `FileDesc` is dropped.
42     ///
43     /// Note that all I/O operations done on this object will be *blocking*, but
44     /// they do not require the runtime to be active.
45     pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc {
46         FileDesc { inner: Arc::new(Inner {
47             fd: fd,
48             close_on_drop: close_on_drop
49         }) }
50     }
51
52     pub fn inner_read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
53         let mut read = 0;
54         let ret = unsafe {
55             libc::ReadFile(self.handle(), buf.as_ptr() as libc::LPVOID,
56                            buf.len() as libc::DWORD, &mut read,
57                            ptr::mut_null())
58         };
59         if ret != 0 {
60             Ok(read as uint)
61         } else {
62             Err(super::last_error())
63         }
64     }
65     pub fn inner_write(&mut self, buf: &[u8]) -> IoResult<()> {
66         let mut cur = buf.as_ptr();
67         let mut remaining = buf.len();
68         while remaining > 0 {
69             let mut amt = 0;
70             let ret = unsafe {
71                 libc::WriteFile(self.handle(), cur as libc::LPVOID,
72                                 remaining as libc::DWORD, &mut amt,
73                                 ptr::mut_null())
74             };
75             if ret != 0 {
76                 remaining -= amt as uint;
77                 cur = unsafe { cur.offset(amt as int) };
78             } else {
79                 return Err(super::last_error())
80             }
81         }
82         Ok(())
83     }
84
85     pub fn fd(&self) -> fd_t { self.inner.fd }
86
87     pub fn handle(&self) -> libc::HANDLE {
88         unsafe { libc::get_osfhandle(self.fd()) as libc::HANDLE }
89     }
90
91     // A version of seek that takes &self so that tell can call it
92     //   - the private seek should of course take &mut self.
93     fn seek_common(&self, pos: i64, style: rtio::SeekStyle) -> IoResult<u64> {
94         let whence = match style {
95             rtio::SeekSet => libc::FILE_BEGIN,
96             rtio::SeekEnd => libc::FILE_END,
97             rtio::SeekCur => libc::FILE_CURRENT,
98         };
99         unsafe {
100             let mut newpos = 0;
101             match libc::SetFilePointerEx(self.handle(), pos, &mut newpos,
102                                          whence) {
103                 0 => Err(super::last_error()),
104                 _ => Ok(newpos as u64),
105             }
106         }
107     }
108
109 }
110
111 impl rtio::RtioFileStream for FileDesc {
112     fn read(&mut self, buf: &mut [u8]) -> IoResult<int> {
113         self.inner_read(buf).map(|i| i as int)
114     }
115     fn write(&mut self, buf: &[u8]) -> IoResult<()> {
116         self.inner_write(buf)
117     }
118
119     fn pread(&mut self, buf: &mut [u8], offset: u64) -> IoResult<int> {
120         let mut read = 0;
121         let mut overlap: libc::OVERLAPPED = unsafe { mem::zeroed() };
122         overlap.Offset = offset as libc::DWORD;
123         overlap.OffsetHigh = (offset >> 32) as libc::DWORD;
124         let ret = unsafe {
125             libc::ReadFile(self.handle(), buf.as_ptr() as libc::LPVOID,
126                            buf.len() as libc::DWORD, &mut read,
127                            &mut overlap)
128         };
129         if ret != 0 {
130             Ok(read as int)
131         } else {
132             Err(super::last_error())
133         }
134     }
135     fn pwrite(&mut self, buf: &[u8], mut offset: u64) -> IoResult<()> {
136         let mut cur = buf.as_ptr();
137         let mut remaining = buf.len();
138         let mut overlap: libc::OVERLAPPED = unsafe { mem::zeroed() };
139         while remaining > 0 {
140             overlap.Offset = offset as libc::DWORD;
141             overlap.OffsetHigh = (offset >> 32) as libc::DWORD;
142             let mut amt = 0;
143             let ret = unsafe {
144                 libc::WriteFile(self.handle(), cur as libc::LPVOID,
145                                 remaining as libc::DWORD, &mut amt,
146                                 &mut overlap)
147             };
148             if ret != 0 {
149                 remaining -= amt as uint;
150                 cur = unsafe { cur.offset(amt as int) };
151                 offset += amt as u64;
152             } else {
153                 return Err(super::last_error())
154             }
155         }
156         Ok(())
157     }
158
159     fn seek(&mut self, pos: i64, style: rtio::SeekStyle) -> IoResult<u64> {
160         self.seek_common(pos, style)
161     }
162
163     fn tell(&self) -> IoResult<u64> {
164         self.seek_common(0, rtio::SeekCur)
165     }
166
167     fn fsync(&mut self) -> IoResult<()> {
168         super::mkerr_winbool(unsafe {
169             libc::FlushFileBuffers(self.handle())
170         })
171     }
172
173     fn datasync(&mut self) -> IoResult<()> { return self.fsync(); }
174
175     fn truncate(&mut self, offset: i64) -> IoResult<()> {
176         let orig_pos = try!(self.tell());
177         let _ = try!(self.seek(offset, rtio::SeekSet));
178         let ret = unsafe {
179             match libc::SetEndOfFile(self.handle()) {
180                 0 => Err(super::last_error()),
181                 _ => Ok(())
182             }
183         };
184         let _ = self.seek(orig_pos as i64, rtio::SeekSet);
185         return ret;
186     }
187
188     fn fstat(&mut self) -> IoResult<rtio::FileStat> {
189         let mut stat: libc::stat = unsafe { mem::zeroed() };
190         match unsafe { libc::fstat(self.fd(), &mut stat) } {
191             0 => Ok(mkstat(&stat)),
192             _ => Err(super::last_error()),
193         }
194     }
195 }
196
197 impl rtio::RtioPipe for FileDesc {
198     fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
199         self.inner_read(buf)
200     }
201     fn write(&mut self, buf: &[u8]) -> IoResult<()> {
202         self.inner_write(buf)
203     }
204     fn clone(&self) -> Box<rtio::RtioPipe:Send> {
205         box FileDesc { inner: self.inner.clone() } as Box<rtio::RtioPipe:Send>
206     }
207
208     // Only supported on named pipes currently. Note that this doesn't have an
209     // impact on the std::io primitives, this is never called via
210     // std::io::PipeStream. If the functionality is exposed in the future, then
211     // these methods will need to be implemented.
212     fn close_read(&mut self) -> IoResult<()> {
213         Err(super::unimpl())
214     }
215     fn close_write(&mut self) -> IoResult<()> {
216         Err(super::unimpl())
217     }
218     fn set_timeout(&mut self, _t: Option<u64>) {}
219     fn set_read_timeout(&mut self, _t: Option<u64>) {}
220     fn set_write_timeout(&mut self, _t: Option<u64>) {}
221 }
222
223 impl rtio::RtioTTY for FileDesc {
224     fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
225         self.inner_read(buf)
226     }
227     fn write(&mut self, buf: &[u8]) -> IoResult<()> {
228         self.inner_write(buf)
229     }
230     fn set_raw(&mut self, _raw: bool) -> IoResult<()> {
231         Err(super::unimpl())
232     }
233     fn get_winsize(&mut self) -> IoResult<(int, int)> {
234         Err(super::unimpl())
235     }
236     fn isatty(&self) -> bool { false }
237 }
238
239 impl Drop for Inner {
240     fn drop(&mut self) {
241         // closing stdio file handles makes no sense, so never do it. Also, note
242         // that errors are ignored when closing a file descriptor. The reason
243         // for this is that if an error occurs we don't actually know if the
244         // file descriptor was closed or not, and if we retried (for something
245         // like EINTR), we might close another valid file descriptor (opened
246         // after we closed ours.
247         if self.close_on_drop && self.fd > libc::STDERR_FILENO {
248             let n = unsafe { libc::close(self.fd) };
249             if n != 0 {
250                 println!("error {} when closing file descriptor {}", n, self.fd);
251             }
252         }
253     }
254 }
255
256 pub fn to_utf16(s: &CString) -> IoResult<Vec<u16>> {
257     match s.as_str() {
258         Some(s) => Ok(s.to_utf16().append_one(0)),
259         None => Err(IoError {
260             code: libc::ERROR_INVALID_NAME as uint,
261             extra: 0,
262             detail: Some("valid unicode input required".to_str()),
263         })
264     }
265 }
266
267 pub fn open(path: &CString, fm: rtio::FileMode, fa: rtio::FileAccess)
268         -> IoResult<FileDesc> {
269     // Flags passed to open_osfhandle
270     let flags = match fm {
271         rtio::Open => 0,
272         rtio::Append => libc::O_APPEND,
273         rtio::Truncate => libc::O_TRUNC,
274     };
275     let flags = match fa {
276         rtio::Read => flags | libc::O_RDONLY,
277         rtio::Write => flags | libc::O_WRONLY | libc::O_CREAT,
278         rtio::ReadWrite => flags | libc::O_RDWR | libc::O_CREAT,
279     };
280
281     let mut dwDesiredAccess = match fa {
282         rtio::Read => libc::FILE_GENERIC_READ,
283         rtio::Write => libc::FILE_GENERIC_WRITE,
284         rtio::ReadWrite => libc::FILE_GENERIC_READ | libc::FILE_GENERIC_WRITE
285     };
286
287     // libuv has a good comment about this, but the basic idea is what we try to
288     // emulate unix semantics by enabling all sharing by allowing things such as
289     // deleting a file while it's still open.
290     let dwShareMode = libc::FILE_SHARE_READ | libc::FILE_SHARE_WRITE |
291                       libc::FILE_SHARE_DELETE;
292
293     let dwCreationDisposition = match (fm, fa) {
294         (rtio::Truncate, rtio::Read) => libc::TRUNCATE_EXISTING,
295         (rtio::Truncate, _) => libc::CREATE_ALWAYS,
296         (rtio::Open, rtio::Read) => libc::OPEN_EXISTING,
297         (rtio::Open, _) => libc::OPEN_ALWAYS,
298         (rtio::Append, rtio::Read) => {
299             dwDesiredAccess |= libc::FILE_APPEND_DATA;
300             libc::OPEN_EXISTING
301         }
302         (rtio::Append, _) => {
303             dwDesiredAccess &= !libc::FILE_WRITE_DATA;
304             dwDesiredAccess |= libc::FILE_APPEND_DATA;
305             libc::OPEN_ALWAYS
306         }
307     };
308
309     let mut dwFlagsAndAttributes = libc::FILE_ATTRIBUTE_NORMAL;
310     // Compat with unix, this allows opening directories (see libuv)
311     dwFlagsAndAttributes |= libc::FILE_FLAG_BACKUP_SEMANTICS;
312
313     let path = try!(to_utf16(path));
314     let handle = unsafe {
315         libc::CreateFileW(path.as_ptr(),
316                           dwDesiredAccess,
317                           dwShareMode,
318                           ptr::mut_null(),
319                           dwCreationDisposition,
320                           dwFlagsAndAttributes,
321                           ptr::mut_null())
322     };
323     if handle == libc::INVALID_HANDLE_VALUE as libc::HANDLE {
324         Err(super::last_error())
325     } else {
326         let fd = unsafe {
327             libc::open_osfhandle(handle as libc::intptr_t, flags)
328         };
329         if fd < 0 {
330             let _ = unsafe { libc::CloseHandle(handle) };
331             Err(super::last_error())
332         } else {
333             Ok(FileDesc::new(fd, true))
334         }
335     }
336 }
337
338 pub fn mkdir(p: &CString, _mode: uint) -> IoResult<()> {
339     let p = try!(to_utf16(p));
340     super::mkerr_winbool(unsafe {
341         // FIXME: turn mode into something useful? #2623
342         libc::CreateDirectoryW(p.as_ptr(), ptr::mut_null())
343     })
344 }
345
346 pub fn readdir(p: &CString) -> IoResult<Vec<CString>> {
347     use std::rt::libc_heap::malloc_raw;
348
349     fn prune(root: &CString, dirs: Vec<Path>) -> Vec<CString> {
350         let root = unsafe { CString::new(root.with_ref(|p| p), false) };
351         let root = Path::new(root);
352
353         dirs.move_iter().filter(|path| {
354             path.as_vec() != bytes!(".") && path.as_vec() != bytes!("..")
355         }).map(|path| root.join(path).to_c_str()).collect()
356     }
357
358     extern {
359         fn rust_list_dir_wfd_size() -> libc::size_t;
360         fn rust_list_dir_wfd_fp_buf(wfd: *libc::c_void) -> *u16;
361     }
362     let star = Path::new(unsafe {
363         CString::new(p.with_ref(|p| p), false)
364     }).join("*");
365     let path = try!(to_utf16(&star.to_c_str()));
366
367     unsafe {
368         let wfd_ptr = malloc_raw(rust_list_dir_wfd_size() as uint);
369         let find_handle = libc::FindFirstFileW(path.as_ptr(), wfd_ptr as libc::HANDLE);
370         if find_handle as libc::c_int != libc::INVALID_HANDLE_VALUE {
371             let mut paths = vec!();
372             let mut more_files = 1 as libc::c_int;
373             while more_files != 0 {
374                 let fp_buf = rust_list_dir_wfd_fp_buf(wfd_ptr as *c_void);
375                 if fp_buf as uint == 0 {
376                     fail!("os::list_dir() failure: got null ptr from wfd");
377                 } else {
378                     let fp_vec = vec::raw::from_buf(fp_buf, libc::wcslen(fp_buf) as uint);
379                     let fp_trimmed = str::truncate_utf16_at_nul(fp_vec.as_slice());
380                     let fp_str = str::from_utf16(fp_trimmed)
381                             .expect("rust_list_dir_wfd_fp_buf returned invalid UTF-16");
382                     paths.push(Path::new(fp_str));
383                 }
384                 more_files = libc::FindNextFileW(find_handle,
385                                                  wfd_ptr as libc::HANDLE);
386             }
387             assert!(libc::FindClose(find_handle) != 0);
388             libc::free(wfd_ptr as *mut c_void);
389             Ok(prune(p, paths))
390         } else {
391             Err(super::last_error())
392         }
393     }
394 }
395
396 pub fn unlink(p: &CString) -> IoResult<()> {
397     let p = try!(to_utf16(p));
398     super::mkerr_winbool(unsafe {
399         libc::DeleteFileW(p.as_ptr())
400     })
401 }
402
403 pub fn rename(old: &CString, new: &CString) -> IoResult<()> {
404     let old = try!(to_utf16(old));
405     let new = try!(to_utf16(new));
406     super::mkerr_winbool(unsafe {
407         libc::MoveFileExW(old.as_ptr(), new.as_ptr(),
408                           libc::MOVEFILE_REPLACE_EXISTING)
409     })
410 }
411
412 pub fn chmod(p: &CString, mode: uint) -> IoResult<()> {
413     let p = try!(to_utf16(p));
414     super::mkerr_libc(unsafe {
415         libc::wchmod(p.as_ptr(), mode as libc::c_int)
416     })
417 }
418
419 pub fn rmdir(p: &CString) -> IoResult<()> {
420     let p = try!(to_utf16(p));
421     super::mkerr_libc(unsafe { libc::wrmdir(p.as_ptr()) })
422 }
423
424 pub fn chown(_p: &CString, _uid: int, _gid: int) -> IoResult<()> {
425     // libuv has this as a no-op, so seems like this should as well?
426     Ok(())
427 }
428
429 pub fn readlink(p: &CString) -> IoResult<CString> {
430     // FIXME: I have a feeling that this reads intermediate symlinks as well.
431     use io::c::compat::kernel32::GetFinalPathNameByHandleW;
432     let p = try!(to_utf16(p));
433     let handle = unsafe {
434         libc::CreateFileW(p.as_ptr(),
435                           libc::GENERIC_READ,
436                           libc::FILE_SHARE_READ,
437                           ptr::mut_null(),
438                           libc::OPEN_EXISTING,
439                           libc::FILE_ATTRIBUTE_NORMAL,
440                           ptr::mut_null())
441     };
442     if handle as int == libc::INVALID_HANDLE_VALUE as int {
443         return Err(super::last_error())
444     }
445     // Specify (sz - 1) because the documentation states that it's the size
446     // without the null pointer
447     let ret = fill_utf16_buf_and_decode(|buf, sz| unsafe {
448         GetFinalPathNameByHandleW(handle,
449                                   buf as *u16,
450                                   sz - 1,
451                                   libc::VOLUME_NAME_DOS)
452     });
453     let ret = match ret {
454         Some(ref s) if s.as_slice().starts_with(r"\\?\") => {
455             Ok(Path::new(s.as_slice().slice_from(4)).to_c_str())
456         }
457         Some(s) => Ok(Path::new(s).to_c_str()),
458         None => Err(super::last_error()),
459     };
460     assert!(unsafe { libc::CloseHandle(handle) } != 0);
461     return ret;
462 }
463
464 pub fn symlink(src: &CString, dst: &CString) -> IoResult<()> {
465     use io::c::compat::kernel32::CreateSymbolicLinkW;
466     let src = try!(to_utf16(src));
467     let dst = try!(to_utf16(dst));
468     super::mkerr_winbool(unsafe {
469         CreateSymbolicLinkW(dst.as_ptr(), src.as_ptr(), 0) as libc::BOOL
470     })
471 }
472
473 pub fn link(src: &CString, dst: &CString) -> IoResult<()> {
474     let src = try!(to_utf16(src));
475     let dst = try!(to_utf16(dst));
476     super::mkerr_winbool(unsafe {
477         libc::CreateHardLinkW(dst.as_ptr(), src.as_ptr(), ptr::mut_null())
478     })
479 }
480
481 fn mkstat(stat: &libc::stat) -> rtio::FileStat {
482     rtio::FileStat {
483         size: stat.st_size as u64,
484         kind: stat.st_mode as u64,
485         perm: stat.st_mode as u64,
486         created: stat.st_ctime as u64,
487         modified: stat.st_mtime as u64,
488         accessed: stat.st_atime as u64,
489         device: stat.st_dev as u64,
490         inode: stat.st_ino as u64,
491         rdev: stat.st_rdev as u64,
492         nlink: stat.st_nlink as u64,
493         uid: stat.st_uid as u64,
494         gid: stat.st_gid as u64,
495         blksize: 0,
496         blocks: 0,
497         flags: 0,
498         gen: 0,
499     }
500 }
501
502 pub fn stat(p: &CString) -> IoResult<rtio::FileStat> {
503     let mut stat: libc::stat = unsafe { mem::zeroed() };
504     let p = try!(to_utf16(p));
505     match unsafe { libc::wstat(p.as_ptr(), &mut stat) } {
506         0 => Ok(mkstat(&stat)),
507         _ => Err(super::last_error()),
508     }
509 }
510
511 pub fn lstat(_p: &CString) -> IoResult<rtio::FileStat> {
512     // FIXME: implementation is missing
513     Err(super::unimpl())
514 }
515
516 pub fn utime(p: &CString, atime: u64, mtime: u64) -> IoResult<()> {
517     let buf = libc::utimbuf {
518         actime: (atime / 1000) as libc::time64_t,
519         modtime: (mtime / 1000) as libc::time64_t,
520     };
521     let p = try!(to_utf16(p));
522     super::mkerr_libc(unsafe {
523         libc::wutime(p.as_ptr(), &buf)
524     })
525 }