]> git.lizzy.rs Git - rust.git/blob - src/libstd/sys/windows/fs.rs
rollup merge of #20518: nagisa/weighted-bool
[rust.git] / src / libstd / sys / windows / fs.rs
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.
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 Windows-based file I/O
12
13 use alloc::arc::Arc;
14 use libc::{self, c_int};
15
16 use mem;
17 use sys::os::fill_utf16_buf_and_decode;
18 use path;
19 use ptr;
20 use str;
21 use io;
22
23 use prelude::v1::*;
24 use sys;
25 use sys::os;
26 use sys_common::{keep_going, eof, mkerr_libc};
27
28 use io::{FilePermission, Write, UnstableFileStat, Open, FileAccess, FileMode};
29 use io::{IoResult, IoError, FileStat, SeekStyle};
30 use io::{Read, Truncate, SeekCur, SeekSet, ReadWrite, SeekEnd, Append};
31
32 pub type fd_t = libc::c_int;
33
34 pub struct FileDesc {
35     /// The underlying C file descriptor.
36     pub fd: fd_t,
37
38     /// Whether to close the file descriptor on drop.
39     close_on_drop: bool,
40 }
41
42 impl FileDesc {
43     pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc {
44         FileDesc { fd: fd, close_on_drop: close_on_drop }
45     }
46
47     pub fn read(&self, buf: &mut [u8]) -> IoResult<uint> {
48         let mut read = 0;
49         let ret = unsafe {
50             libc::ReadFile(self.handle(), buf.as_ptr() as libc::LPVOID,
51                            buf.len() as libc::DWORD, &mut read,
52                            ptr::null_mut())
53         };
54         if ret != 0 {
55             Ok(read as uint)
56         } else {
57             Err(super::last_error())
58         }
59     }
60
61     pub fn write(&self, buf: &[u8]) -> IoResult<()> {
62         let mut cur = buf.as_ptr();
63         let mut remaining = buf.len();
64         while remaining > 0 {
65             let mut amt = 0;
66             let ret = unsafe {
67                 libc::WriteFile(self.handle(), cur as libc::LPVOID,
68                                 remaining as libc::DWORD, &mut amt,
69                                 ptr::null_mut())
70             };
71             if ret != 0 {
72                 remaining -= amt as uint;
73                 cur = unsafe { cur.offset(amt as int) };
74             } else {
75                 return Err(super::last_error())
76             }
77         }
78         Ok(())
79     }
80
81     pub fn fd(&self) -> fd_t { self.fd }
82
83     pub fn handle(&self) -> libc::HANDLE {
84         unsafe { libc::get_osfhandle(self.fd()) as libc::HANDLE }
85     }
86
87     // A version of seek that takes &self so that tell can call it
88     //   - the private seek should of course take &mut self.
89     fn seek_common(&self, pos: i64, style: SeekStyle) -> IoResult<u64> {
90         let whence = match style {
91             SeekSet => libc::FILE_BEGIN,
92             SeekEnd => libc::FILE_END,
93             SeekCur => libc::FILE_CURRENT,
94         };
95         unsafe {
96             let mut newpos = 0;
97             match libc::SetFilePointerEx(self.handle(), pos, &mut newpos, whence) {
98                 0 => Err(super::last_error()),
99                 _ => Ok(newpos as u64),
100             }
101         }
102     }
103
104     pub fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<u64> {
105         self.seek_common(pos, style)
106     }
107
108     pub fn tell(&self) -> IoResult<u64> {
109         self.seek_common(0, SeekCur)
110     }
111
112     pub fn fsync(&mut self) -> IoResult<()> {
113         super::mkerr_winbool(unsafe {
114             libc::FlushFileBuffers(self.handle())
115         })
116     }
117
118     pub fn datasync(&mut self) -> IoResult<()> { return self.fsync(); }
119
120     pub fn truncate(&mut self, offset: i64) -> IoResult<()> {
121         let orig_pos = try!(self.tell());
122         let _ = try!(self.seek(offset, SeekSet));
123         let ret = unsafe {
124             match libc::SetEndOfFile(self.handle()) {
125                 0 => Err(super::last_error()),
126                 _ => Ok(())
127             }
128         };
129         let _ = self.seek(orig_pos as i64, SeekSet);
130         return ret;
131     }
132
133     pub fn fstat(&self) -> IoResult<io::FileStat> {
134         let mut stat: libc::stat = unsafe { mem::zeroed() };
135         match unsafe { libc::fstat(self.fd(), &mut stat) } {
136             0 => Ok(mkstat(&stat)),
137             _ => Err(super::last_error()),
138         }
139     }
140
141     /// Extract the actual filedescriptor without closing it.
142     pub fn unwrap(self) -> fd_t {
143         let fd = self.fd;
144         unsafe { mem::forget(self) };
145         fd
146     }
147 }
148
149 impl Drop for FileDesc {
150     fn drop(&mut self) {
151         // closing stdio file handles makes no sense, so never do it. Also, note
152         // that errors are ignored when closing a file descriptor. The reason
153         // for this is that if an error occurs we don't actually know if the
154         // file descriptor was closed or not, and if we retried (for something
155         // like EINTR), we might close another valid file descriptor (opened
156         // after we closed ours.
157         if self.close_on_drop && self.fd > libc::STDERR_FILENO {
158             let n = unsafe { libc::close(self.fd) };
159             if n != 0 {
160                 println!("error {} when closing file descriptor {}", n, self.fd);
161             }
162         }
163     }
164 }
165
166 pub fn to_utf16(s: &Path) -> IoResult<Vec<u16>> {
167     sys::to_utf16(s.as_str())
168 }
169
170 pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult<FileDesc> {
171     // Flags passed to open_osfhandle
172     let flags = match fm {
173         Open => 0,
174         Append => libc::O_APPEND,
175         Truncate => libc::O_TRUNC,
176     };
177     let flags = match fa {
178         Read => flags | libc::O_RDONLY,
179         Write => flags | libc::O_WRONLY | libc::O_CREAT,
180         ReadWrite => flags | libc::O_RDWR | libc::O_CREAT,
181     };
182     let mut dwDesiredAccess = match fa {
183         Read => libc::FILE_GENERIC_READ,
184         Write => libc::FILE_GENERIC_WRITE,
185         ReadWrite => libc::FILE_GENERIC_READ | libc::FILE_GENERIC_WRITE
186     };
187
188     // libuv has a good comment about this, but the basic idea is what we try to
189     // emulate unix semantics by enabling all sharing by allowing things such as
190     // deleting a file while it's still open.
191     let dwShareMode = libc::FILE_SHARE_READ | libc::FILE_SHARE_WRITE |
192                       libc::FILE_SHARE_DELETE;
193
194     let dwCreationDisposition = match (fm, fa) {
195         (Truncate, Read) => libc::TRUNCATE_EXISTING,
196         (Truncate, _) => libc::CREATE_ALWAYS,
197         (Open, Read) => libc::OPEN_EXISTING,
198         (Open, _) => libc::OPEN_ALWAYS,
199         (Append, Read) => {
200             dwDesiredAccess |= libc::FILE_APPEND_DATA;
201             libc::OPEN_EXISTING
202         }
203         (Append, _) => {
204             dwDesiredAccess &= !libc::FILE_WRITE_DATA;
205             dwDesiredAccess |= libc::FILE_APPEND_DATA;
206             libc::OPEN_ALWAYS
207         }
208     };
209
210     let mut dwFlagsAndAttributes = libc::FILE_ATTRIBUTE_NORMAL;
211     // Compat with unix, this allows opening directories (see libuv)
212     dwFlagsAndAttributes |= libc::FILE_FLAG_BACKUP_SEMANTICS;
213
214     let path = try!(to_utf16(path));
215     let handle = unsafe {
216         libc::CreateFileW(path.as_ptr(),
217                           dwDesiredAccess,
218                           dwShareMode,
219                           ptr::null_mut(),
220                           dwCreationDisposition,
221                           dwFlagsAndAttributes,
222                           ptr::null_mut())
223     };
224     if handle == libc::INVALID_HANDLE_VALUE {
225         Err(super::last_error())
226     } else {
227         let fd = unsafe {
228             libc::open_osfhandle(handle as libc::intptr_t, flags)
229         };
230         if fd < 0 {
231             let _ = unsafe { libc::CloseHandle(handle) };
232             Err(super::last_error())
233         } else {
234             Ok(FileDesc::new(fd, true))
235         }
236     }
237 }
238
239 pub fn mkdir(p: &Path, _mode: uint) -> IoResult<()> {
240     let p = try!(to_utf16(p));
241     super::mkerr_winbool(unsafe {
242         // FIXME: turn mode into something useful? #2623
243         libc::CreateDirectoryW(p.as_ptr(), ptr::null_mut())
244     })
245 }
246
247 pub fn readdir(p: &Path) -> IoResult<Vec<Path>> {
248     fn prune(root: &Path, dirs: Vec<Path>) -> Vec<Path> {
249         dirs.into_iter().filter(|path| {
250             path.as_vec() != b"." && path.as_vec() != b".."
251         }).map(|path| root.join(path)).collect()
252     }
253
254     let star = p.join("*");
255     let path = try!(to_utf16(&star));
256
257     unsafe {
258         let mut wfd = mem::zeroed();
259         let find_handle = libc::FindFirstFileW(path.as_ptr(), &mut wfd);
260         if find_handle != libc::INVALID_HANDLE_VALUE {
261             let mut paths = vec![];
262             let mut more_files = 1 as libc::BOOL;
263             while more_files != 0 {
264                 {
265                     let filename = os::truncate_utf16_at_nul(&wfd.cFileName);
266                     match String::from_utf16(filename) {
267                         Ok(filename) => paths.push(Path::new(filename)),
268                         Err(..) => {
269                             assert!(libc::FindClose(find_handle) != 0);
270                             return Err(IoError {
271                                 kind: io::InvalidInput,
272                                 desc: "path was not valid UTF-16",
273                                 detail: Some(format!("path was not valid UTF-16: {}", filename)),
274                             })
275                         }, // FIXME #12056: Convert the UCS-2 to invalid utf-8 instead of erroring
276                     }
277                 }
278                 more_files = libc::FindNextFileW(find_handle, &mut wfd);
279             }
280             assert!(libc::FindClose(find_handle) != 0);
281             Ok(prune(p, paths))
282         } else {
283             Err(super::last_error())
284         }
285     }
286 }
287
288 pub fn unlink(p: &Path) -> IoResult<()> {
289     fn do_unlink(p_utf16: &Vec<u16>) -> IoResult<()> {
290         super::mkerr_winbool(unsafe { libc::DeleteFileW(p_utf16.as_ptr()) })
291     }
292
293     let p_utf16 = try!(to_utf16(p));
294     let res = do_unlink(&p_utf16);
295     match res {
296         Ok(()) => Ok(()),
297         Err(e) => {
298             // FIXME: change the code below to use more direct calls
299             // than `stat` and `chmod`, to avoid re-conversion to
300             // utf16 etc.
301
302             // On unix, a readonly file can be successfully removed. On windows,
303             // however, it cannot. To keep the two platforms in line with
304             // respect to their behavior, catch this case on windows, attempt to
305             // change it to read-write, and then remove the file.
306             if e.kind == io::PermissionDenied {
307                 let stat = match stat(p) {
308                     Ok(stat) => stat,
309                     Err(..) => return Err(e),
310                 };
311                 if stat.perm.intersects(io::USER_WRITE) { return Err(e) }
312
313                 match chmod(p, (stat.perm | io::USER_WRITE).bits() as uint) {
314                     Ok(()) => do_unlink(&p_utf16),
315                     Err(..) => {
316                         // Try to put it back as we found it
317                         let _ = chmod(p, stat.perm.bits() as uint);
318                         Err(e)
319                     }
320                 }
321             } else {
322                 Err(e)
323             }
324         }
325     }
326 }
327
328 pub fn rename(old: &Path, new: &Path) -> IoResult<()> {
329     let old = try!(to_utf16(old));
330     let new = try!(to_utf16(new));
331     super::mkerr_winbool(unsafe {
332         libc::MoveFileExW(old.as_ptr(), new.as_ptr(), libc::MOVEFILE_REPLACE_EXISTING)
333     })
334 }
335
336 pub fn chmod(p: &Path, mode: uint) -> IoResult<()> {
337     let p = try!(to_utf16(p));
338     mkerr_libc(unsafe {
339         libc::wchmod(p.as_ptr(), mode as libc::c_int)
340     })
341 }
342
343 pub fn rmdir(p: &Path) -> IoResult<()> {
344     let p = try!(to_utf16(p));
345     mkerr_libc(unsafe { libc::wrmdir(p.as_ptr()) })
346 }
347
348 pub fn chown(_p: &Path, _uid: int, _gid: int) -> IoResult<()> {
349     // libuv has this as a no-op, so seems like this should as well?
350     Ok(())
351 }
352
353 pub fn readlink(p: &Path) -> IoResult<Path> {
354     // FIXME: I have a feeling that this reads intermediate symlinks as well.
355     use sys::c::compat::kernel32::GetFinalPathNameByHandleW;
356     let p = try!(to_utf16(p));
357     let handle = unsafe {
358         libc::CreateFileW(p.as_ptr(),
359                           libc::GENERIC_READ,
360                           libc::FILE_SHARE_READ,
361                           ptr::null_mut(),
362                           libc::OPEN_EXISTING,
363                           libc::FILE_ATTRIBUTE_NORMAL,
364                           ptr::null_mut())
365     };
366     if handle == libc::INVALID_HANDLE_VALUE {
367         return Err(super::last_error())
368     }
369     // Specify (sz - 1) because the documentation states that it's the size
370     // without the null pointer
371     let ret = fill_utf16_buf_and_decode(|buf, sz| unsafe {
372         GetFinalPathNameByHandleW(handle,
373                                   buf as *const u16,
374                                   sz - 1,
375                                   libc::VOLUME_NAME_DOS)
376     });
377     let ret = match ret {
378         Some(ref s) if s.starts_with(r"\\?\") => { // "
379             Ok(Path::new(s.slice_from(4)))
380         }
381         Some(s) => Ok(Path::new(s)),
382         None => Err(super::last_error()),
383     };
384     assert!(unsafe { libc::CloseHandle(handle) } != 0);
385     return ret;
386 }
387
388 pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> {
389     use sys::c::compat::kernel32::CreateSymbolicLinkW;
390     let src = try!(to_utf16(src));
391     let dst = try!(to_utf16(dst));
392     super::mkerr_winbool(unsafe {
393         CreateSymbolicLinkW(dst.as_ptr(), src.as_ptr(), 0) as libc::BOOL
394     })
395 }
396
397 pub fn link(src: &Path, dst: &Path) -> IoResult<()> {
398     let src = try!(to_utf16(src));
399     let dst = try!(to_utf16(dst));
400     super::mkerr_winbool(unsafe {
401         libc::CreateHardLinkW(dst.as_ptr(), src.as_ptr(), ptr::null_mut())
402     })
403 }
404
405 fn mkstat(stat: &libc::stat) -> FileStat {
406     FileStat {
407         size: stat.st_size as u64,
408         kind: match (stat.st_mode as libc::c_int) & libc::S_IFMT {
409             libc::S_IFREG => io::FileType::RegularFile,
410             libc::S_IFDIR => io::FileType::Directory,
411             libc::S_IFIFO => io::FileType::NamedPipe,
412             libc::S_IFBLK => io::FileType::BlockSpecial,
413             libc::S_IFLNK => io::FileType::Symlink,
414             _ => io::FileType::Unknown,
415         },
416         perm: FilePermission::from_bits_truncate(stat.st_mode as u32),
417         created: stat.st_ctime as u64,
418         modified: stat.st_mtime as u64,
419         accessed: stat.st_atime as u64,
420         unstable: UnstableFileStat {
421             device: stat.st_dev as u64,
422             inode: stat.st_ino as u64,
423             rdev: stat.st_rdev as u64,
424             nlink: stat.st_nlink as u64,
425             uid: stat.st_uid as u64,
426             gid: stat.st_gid as u64,
427             blksize:0,
428             blocks: 0,
429             flags: 0,
430             gen: 0,
431         },
432     }
433 }
434
435 pub fn stat(p: &Path) -> IoResult<FileStat> {
436     let mut stat: libc::stat = unsafe { mem::zeroed() };
437     let p = try!(to_utf16(p));
438     match unsafe { libc::wstat(p.as_ptr(), &mut stat) } {
439         0 => Ok(mkstat(&stat)),
440         _ => Err(super::last_error()),
441     }
442 }
443
444 // FIXME: move this to platform-specific modules (for now)?
445 pub fn lstat(_p: &Path) -> IoResult<FileStat> {
446     // FIXME: implementation is missing
447     Err(super::unimpl())
448 }
449
450 pub fn utime(p: &Path, atime: u64, mtime: u64) -> IoResult<()> {
451     let mut buf = libc::utimbuf {
452         actime: atime as libc::time64_t,
453         modtime: mtime as libc::time64_t,
454     };
455     let p = try!(to_utf16(p));
456     mkerr_libc(unsafe {
457         libc::wutime(p.as_ptr(), &mut buf)
458     })
459 }