]> git.lizzy.rs Git - rust.git/blob - src/libstd/sys/windows/fs.rs
Merge pull request #20510 from tshepang/patch-6
[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 c_str::CString;
17 use mem;
18 use sys::os::fill_utf16_buf_and_decode;
19 use path;
20 use ptr;
21 use str;
22 use io;
23
24 use prelude::v1::*;
25 use sys;
26 use sys::os;
27 use sys_common::{keep_going, eof, mkerr_libc};
28
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};
32
33 pub type fd_t = libc::c_int;
34
35 pub struct FileDesc {
36     /// The underlying C file descriptor.
37     pub fd: fd_t,
38
39     /// Whether to close the file descriptor on drop.
40     close_on_drop: bool,
41 }
42
43 impl FileDesc {
44     pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc {
45         FileDesc { fd: fd, close_on_drop: close_on_drop }
46     }
47
48     pub fn read(&self, buf: &mut [u8]) -> IoResult<uint> {
49         let mut read = 0;
50         let ret = unsafe {
51             libc::ReadFile(self.handle(), buf.as_ptr() as libc::LPVOID,
52                            buf.len() as libc::DWORD, &mut read,
53                            ptr::null_mut())
54         };
55         if ret != 0 {
56             Ok(read as uint)
57         } else {
58             Err(super::last_error())
59         }
60     }
61
62     pub fn write(&self, buf: &[u8]) -> IoResult<()> {
63         let mut cur = buf.as_ptr();
64         let mut remaining = buf.len();
65         while remaining > 0 {
66             let mut amt = 0;
67             let ret = unsafe {
68                 libc::WriteFile(self.handle(), cur as libc::LPVOID,
69                                 remaining as libc::DWORD, &mut amt,
70                                 ptr::null_mut())
71             };
72             if ret != 0 {
73                 remaining -= amt as uint;
74                 cur = unsafe { cur.offset(amt as int) };
75             } else {
76                 return Err(super::last_error())
77             }
78         }
79         Ok(())
80     }
81
82     pub fn fd(&self) -> fd_t { self.fd }
83
84     pub fn handle(&self) -> libc::HANDLE {
85         unsafe { libc::get_osfhandle(self.fd()) as libc::HANDLE }
86     }
87
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,
95         };
96         unsafe {
97             let mut newpos = 0;
98             match libc::SetFilePointerEx(self.handle(), pos, &mut newpos, whence) {
99                 0 => Err(super::last_error()),
100                 _ => Ok(newpos as u64),
101             }
102         }
103     }
104
105     pub fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<u64> {
106         self.seek_common(pos, style)
107     }
108
109     pub fn tell(&self) -> IoResult<u64> {
110         self.seek_common(0, SeekCur)
111     }
112
113     pub fn fsync(&mut self) -> IoResult<()> {
114         super::mkerr_winbool(unsafe {
115             libc::FlushFileBuffers(self.handle())
116         })
117     }
118
119     pub fn datasync(&mut self) -> IoResult<()> { return self.fsync(); }
120
121     pub fn truncate(&mut self, offset: i64) -> IoResult<()> {
122         let orig_pos = try!(self.tell());
123         let _ = try!(self.seek(offset, SeekSet));
124         let ret = unsafe {
125             match libc::SetEndOfFile(self.handle()) {
126                 0 => Err(super::last_error()),
127                 _ => Ok(())
128             }
129         };
130         let _ = self.seek(orig_pos as i64, SeekSet);
131         return ret;
132     }
133
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()),
139         }
140     }
141
142     /// Extract the actual filedescriptor without closing it.
143     pub fn unwrap(self) -> fd_t {
144         let fd = self.fd;
145         unsafe { mem::forget(self) };
146         fd
147     }
148 }
149
150 impl Drop for FileDesc {
151     fn drop(&mut self) {
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) };
160             if n != 0 {
161                 println!("error {} when closing file descriptor {}", n, self.fd);
162             }
163         }
164     }
165 }
166
167 pub fn to_utf16(s: &Path) -> IoResult<Vec<u16>> {
168     sys::to_utf16(s.as_str())
169 }
170
171 pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult<FileDesc> {
172     // Flags passed to open_osfhandle
173     let flags = match fm {
174         Open => 0,
175         Append => libc::O_APPEND,
176         Truncate => libc::O_TRUNC,
177     };
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,
182     };
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
187     };
188
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;
194
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,
200         (Append, Read) => {
201             dwDesiredAccess |= libc::FILE_APPEND_DATA;
202             libc::OPEN_EXISTING
203         }
204         (Append, _) => {
205             dwDesiredAccess &= !libc::FILE_WRITE_DATA;
206             dwDesiredAccess |= libc::FILE_APPEND_DATA;
207             libc::OPEN_ALWAYS
208         }
209     };
210
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;
214
215     let path = try!(to_utf16(path));
216     let handle = unsafe {
217         libc::CreateFileW(path.as_ptr(),
218                           dwDesiredAccess,
219                           dwShareMode,
220                           ptr::null_mut(),
221                           dwCreationDisposition,
222                           dwFlagsAndAttributes,
223                           ptr::null_mut())
224     };
225     if handle == libc::INVALID_HANDLE_VALUE {
226         Err(super::last_error())
227     } else {
228         let fd = unsafe {
229             libc::open_osfhandle(handle as libc::intptr_t, flags)
230         };
231         if fd < 0 {
232             let _ = unsafe { libc::CloseHandle(handle) };
233             Err(super::last_error())
234         } else {
235             Ok(FileDesc::new(fd, true))
236         }
237     }
238 }
239
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())
245     })
246 }
247
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()
253     }
254
255     let star = p.join("*");
256     let path = try!(to_utf16(&star));
257
258     unsafe {
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 {
265                 {
266                     let filename = os::truncate_utf16_at_nul(&wfd.cFileName);
267                     match String::from_utf16(filename) {
268                         Ok(filename) => paths.push(Path::new(filename)),
269                         Err(..) => {
270                             assert!(libc::FindClose(find_handle) != 0);
271                             return Err(IoError {
272                                 kind: io::InvalidInput,
273                                 desc: "path was not valid UTF-16",
274                                 detail: Some(format!("path was not valid UTF-16: {}", filename)),
275                             })
276                         }, // FIXME #12056: Convert the UCS-2 to invalid utf-8 instead of erroring
277                     }
278                 }
279                 more_files = libc::FindNextFileW(find_handle, &mut wfd);
280             }
281             assert!(libc::FindClose(find_handle) != 0);
282             Ok(prune(p, paths))
283         } else {
284             Err(super::last_error())
285         }
286     }
287 }
288
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()) })
292     }
293
294     let p_utf16 = try!(to_utf16(p));
295     let res = do_unlink(&p_utf16);
296     match res {
297         Ok(()) => Ok(()),
298         Err(e) => {
299             // FIXME: change the code below to use more direct calls
300             // than `stat` and `chmod`, to avoid re-conversion to
301             // utf16 etc.
302
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) {
309                     Ok(stat) => stat,
310                     Err(..) => return Err(e),
311                 };
312                 if stat.perm.intersects(io::USER_WRITE) { return Err(e) }
313
314                 match chmod(p, (stat.perm | io::USER_WRITE).bits() as uint) {
315                     Ok(()) => do_unlink(&p_utf16),
316                     Err(..) => {
317                         // Try to put it back as we found it
318                         let _ = chmod(p, stat.perm.bits() as uint);
319                         Err(e)
320                     }
321                 }
322             } else {
323                 Err(e)
324             }
325         }
326     }
327 }
328
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)
334     })
335 }
336
337 pub fn chmod(p: &Path, mode: uint) -> IoResult<()> {
338     let p = try!(to_utf16(p));
339     mkerr_libc(unsafe {
340         libc::wchmod(p.as_ptr(), mode as libc::c_int)
341     })
342 }
343
344 pub fn rmdir(p: &Path) -> IoResult<()> {
345     let p = try!(to_utf16(p));
346     mkerr_libc(unsafe { libc::wrmdir(p.as_ptr()) })
347 }
348
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?
351     Ok(())
352 }
353
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(),
360                           libc::GENERIC_READ,
361                           libc::FILE_SHARE_READ,
362                           ptr::null_mut(),
363                           libc::OPEN_EXISTING,
364                           libc::FILE_ATTRIBUTE_NORMAL,
365                           ptr::null_mut())
366     };
367     if handle == libc::INVALID_HANDLE_VALUE {
368         return Err(super::last_error())
369     }
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,
374                                   buf as *const u16,
375                                   sz - 1,
376                                   libc::VOLUME_NAME_DOS)
377     });
378     let ret = match ret {
379         Some(ref s) if s.starts_with(r"\\?\") => { // "
380             Ok(Path::new(s.slice_from(4)))
381         }
382         Some(s) => Ok(Path::new(s)),
383         None => Err(super::last_error()),
384     };
385     assert!(unsafe { libc::CloseHandle(handle) } != 0);
386     return ret;
387 }
388
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
395     })
396 }
397
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())
403     })
404 }
405
406 fn mkstat(stat: &libc::stat) -> FileStat {
407     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,
416         },
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,
428             blksize:0,
429             blocks: 0,
430             flags: 0,
431             gen: 0,
432         },
433     }
434 }
435
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()),
442     }
443 }
444
445 // FIXME: move this to platform-specific modules (for now)?
446 pub fn lstat(_p: &Path) -> IoResult<FileStat> {
447     // FIXME: implementation is missing
448     Err(super::unimpl())
449 }
450
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,
455     };
456     let p = try!(to_utf16(p));
457     mkerr_libc(unsafe {
458         libc::wutime(p.as_ptr(), &mut buf)
459     })
460 }