Browse Source

ctru-rs: impl Read and Write for File

pull/10/head
Fenrir 8 years ago
parent
commit
25b2b10ff6
  1. 205
      ctru-rs/src/services/fs.rs

205
ctru-rs/src/services/fs.rs

@ -3,15 +3,18 @@
//! This module contains basic methods to manipulate the contents of the 3DS's filesystem. //! This module contains basic methods to manipulate the contents of the 3DS's filesystem.
//! Only the SD card is currently supported. //! Only the SD card is currently supported.
use std::io::{Read, Write};
use std::io::Error as IoError;
use std::io::Result as IoResult;
use std::io::ErrorKind as IoErrorKind;
use std::ffi::OsString;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ptr; use std::ptr;
use std::slice; use std::slice;
use std::mem; use std::mem;
use std::sync::Arc;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::ffi::OsString; use std::sync::Arc;
use widestring::{WideCString, WideCStr}; use widestring::{WideCString, WideCStr};
@ -107,6 +110,63 @@ pub struct Archive {
/// on what options It was opened with. /// on what options It was opened with.
/// ///
/// Files are automatically closed when they go out of scope. /// Files are automatically closed when they go out of scope.
///
/// # Examples
///
/// Create a new file and write bytes to it:
///
/// ```no_run
/// use std::io::prelude::*;
/// use ctru::services::fs::{Fs, File};
///
/// # fn foo() -> std::io::Result<()> {
/// let fs = Fs::init()?;
/// let sdmc = fs.sdmc()?;
///
/// let mut file = File::create(&sdmc, "/foo.txt")?;
/// file.write_all(b"Hello, world!")?;
/// # Ok(())
/// #}
/// ```
///
/// Read the contents of a file into a `String`::
///
/// ```no_run
/// use std::io::prelude::*;
/// use ctru::services::fs::{Fs, File};
///
/// # fn foo() -> std::io::Result<()> {
/// let fs = Fs::init()?;
/// let sdmc = fs.sdmc()?;
///
/// let mut file = File::open(&sdmc, "/foo.txt")?;
/// let mut contents = String::new();
/// file.read_to_string(&mut contents)?;
/// assert_eq!(contents, "Hello, world!");
/// # Ok(())
/// #}
/// ```
///
/// It can be more efficient to read the contents of a file with a buffered
/// `Read`er. This can be accomplished with `BufReader<R>`:
///
/// ```no_run
/// use std::io::BufReader;
/// use std::io::prelude::*;
/// use ctru::services::fs::{Fs, File};
///
/// # fn foo() -> std::io::Result<()> {
/// let fs = Fs::init()?;
/// let sdmc = fs.sdmc()?;
///
/// let file = File::open(&sdmc, "/foo.txt")?;
/// let mut buf_reader = BufReader::new(file);
/// let mut contents = String::new();
/// buf_reader.read_to_string(&mut contents)?;
/// assert_eq!(contents, "Hello, world!");
/// # Ok(())
/// # }
/// ```
pub struct File { pub struct File {
handle: u32, handle: u32,
offset: u64, offset: u64,
@ -240,11 +300,11 @@ impl Fs {
/// ctrulib services are reference counted, so this function may be called /// ctrulib services are reference counted, so this function may be called
/// as many times as desired and the service will not exit until all /// as many times as desired and the service will not exit until all
/// instances of Fs drop out of scope. /// instances of Fs drop out of scope.
pub fn init() -> Result<Fs, i32> { pub fn init() -> ::Result<Fs> {
unsafe { unsafe {
let r = fsInit(); let r = fsInit();
if r < 0 { if r < 0 {
Err(r) Err(::Error::from(r))
} else { } else {
Ok(Fs { pd: PhantomData }) Ok(Fs { pd: PhantomData })
} }
@ -252,14 +312,14 @@ impl Fs {
} }
/// Returns a handle to the SDMC (memory card) Archive. /// Returns a handle to the SDMC (memory card) Archive.
pub fn sdmc(&self) -> Result<Archive, i32> { pub fn sdmc(&self) -> ::Result<Archive> {
unsafe { unsafe {
let mut handle = 0; let mut handle = 0;
let id = ArchiveID::Sdmc; let id = ArchiveID::Sdmc;
let path = fsMakePath(PathType::Empty.into(), ptr::null() as _); let path = fsMakePath(PathType::Empty.into(), ptr::null() as _);
let r = FSUSER_OpenArchive(&mut handle, id.into(), path); let r = FSUSER_OpenArchive(&mut handle, id.into(), path);
if r < 0 { if r < 0 {
Err(r) Err(::Error::from(r))
} else { } else {
Ok(Archive { Ok(Archive {
handle: handle, handle: handle,
@ -300,7 +360,7 @@ impl File {
/// let sdmc_archive = fs.sdmc().unwrap() /// let sdmc_archive = fs.sdmc().unwrap()
/// let mut f = File::open(&sdmc_archive, "/foo.txt").unwrap(); /// let mut f = File::open(&sdmc_archive, "/foo.txt").unwrap();
/// ``` /// ```
pub fn open<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<File, i32> { pub fn open<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<File> {
OpenOptions::new().read(true).archive(arch).open(path.as_ref()) OpenOptions::new().read(true).archive(arch).open(path.as_ref())
} }
@ -326,7 +386,7 @@ impl File {
/// let sdmc_archive = fs.sdmc().unwrap() /// let sdmc_archive = fs.sdmc().unwrap()
/// let mut f = File::create(&sdmc_archive, "/foo.txt").unwrap(); /// let mut f = File::create(&sdmc_archive, "/foo.txt").unwrap();
/// ``` /// ```
pub fn create<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<File, i32> { pub fn create<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<File> {
OpenOptions::new().write(true).create(true).archive(arch).open(path.as_ref()) OpenOptions::new().write(true).create(true).archive(arch).open(path.as_ref())
} }
@ -335,11 +395,15 @@ impl File {
/// If the size is less than the current file's size, then the file will be shrunk. If it is /// If the size is less than the current file's size, then the file will be shrunk. If it is
/// greater than the current file's size, then the file will be extended to size and have all /// greater than the current file's size, then the file will be extended to size and have all
/// of the intermediate data filled in with 0s. /// of the intermediate data filled in with 0s.
pub fn set_len(&mut self, size: u64) -> Result<(), i32> { ///
/// # Errors
///
/// This function will return an error if the file is not opened for writing.
pub fn set_len(&mut self, size: u64) -> IoResult<()> {
unsafe { unsafe {
let r = FSFILE_SetSize(self.handle, size); let r = FSFILE_SetSize(self.handle, size);
if r < 0 { if r < 0 {
Err(r) Err(IoError::new(IoErrorKind::PermissionDenied, ::Error::from(r)))
} else { } else {
Ok(()) Ok(())
} }
@ -347,26 +411,21 @@ impl File {
} }
/// Queries metadata about the underlying file. /// Queries metadata about the underlying file.
pub fn metadata(&self) -> Result<Metadata, i32> { pub fn metadata(&self) -> IoResult<Metadata> {
// The only metadata we have for files right now is file size. // The only metadata we have for files right now is file size.
// This is likely to change in the future. // This is likely to change in the future.
unsafe { unsafe {
let mut size = 0; let mut size = 0;
let r = FSFILE_GetSize(self.handle, &mut size); let r = FSFILE_GetSize(self.handle, &mut size);
if r < 0 { if r < 0 {
Err(r) Err(IoError::new(IoErrorKind::PermissionDenied, ::Error::from(r)))
} else { } else {
Ok(Metadata { attributes: 0, size: size }) Ok(Metadata { attributes: 0, size: size })
} }
} }
} }
/// Pull some bytes from the file into the specified buffer, returning fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
/// how many bytes were read.
///
/// This function will become private when std::io support is ported
/// to this library.
pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, i32> {
unsafe { unsafe {
let mut n_read = 0; let mut n_read = 0;
let r = FSFILE_Read( let r = FSFILE_Read(
@ -378,28 +437,18 @@ impl File {
); );
self.offset += n_read as u64; self.offset += n_read as u64;
if r < 0 { if r < 0 {
Err(r) Err(IoError::new(IoErrorKind::Other, ::Error::from(r)))
} else { } else {
Ok(n_read as usize) Ok(n_read as usize)
} }
} }
} }
/// Read all bytes until EOF in this source, placing them into buf. fn read_to_end(&mut self, buf: &mut Vec<u8>) -> IoResult<usize> {
/// unsafe { read_to_end_uninitialized(self, buf) }
/// This function will become private when std::io support is ported
/// to this library.
pub fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, i32> {
unsafe {
read_to_end_uninitialized(self, buf)
}
} }
/// Write a buffer into this object, returning how many bytes were written. fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
///
/// This function will become private when std::io support is ported
/// to this library.
pub fn write(&mut self, buf: &[u8]) -> Result<usize, i32> {
unsafe { unsafe {
let mut n_written = 0; let mut n_written = 0;
let r = FSFILE_Write( let r = FSFILE_Write(
@ -408,11 +457,11 @@ impl File {
self.offset, self.offset,
buf.as_ptr() as _, buf.as_ptr() as _,
buf.len() as u32, buf.len() as u32,
FS_WRITE_UPDATE_TIME.bits FS_WRITE_UPDATE_TIME.bits()
); );
self.offset += n_written as u64; self.offset += n_written as u64;
if r < 0 { if r < 0 {
Err(r) Err(IoError::new(IoErrorKind::Other, ::Error::from(r)))
} else { } else {
Ok(n_written as usize) Ok(n_written as usize)
} }
@ -532,31 +581,31 @@ impl OpenOptions {
/// * Invalid combinations of open options. /// * Invalid combinations of open options.
/// ///
/// [`Archive`]: struct.Archive.html /// [`Archive`]: struct.Archive.html
pub fn open<P: AsRef<Path>>(&self, path: P) -> Result<File, i32> { pub fn open<P: AsRef<Path>>(&self, path: P) -> IoResult<File> {
self._open(path.as_ref(), self.get_open_flags()) self._open(path.as_ref(), self.get_open_flags())
} }
fn _open(&self, path: &Path, flags: FsOpen) -> Result<File, i32> { fn _open(&self, path: &Path, flags: FsOpen) -> IoResult<File> {
unsafe { unsafe {
let mut file_handle = 0; let mut file_handle = 0;
let path = to_utf16(path); let path = to_utf16(path);
let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
let r = FSUSER_OpenFile(&mut file_handle, self.arch_handle, fs_path, flags.bits, 0); let r = FSUSER_OpenFile(&mut file_handle, self.arch_handle, fs_path, flags.bits, 0);
if r < 0 { if r < 0 {
return Err(r); return Err(IoError::new(IoErrorKind::Other, ::Error::from(r)));
} }
let mut file = File { handle: file_handle, offset: 0 }; let mut file = File { handle: file_handle, offset: 0 };
if self.append { if self.append {
let metadata = try!(file.metadata()); let metadata = file.metadata()?;
file.offset = metadata.len(); file.offset = metadata.len();
} }
// set the offset to 0 just in case both append and truncate were // set the offset to 0 just in case both append and truncate were
// set to true // set to true
if self.truncate { if self.truncate {
try!(file.set_len(0)); file.set_len(0)?;
file.offset = 0; file.offset = 0;
} }
Ok(file) Ok(file)
@ -577,9 +626,9 @@ impl OpenOptions {
} }
impl<'a> Iterator for ReadDir<'a> { impl<'a> Iterator for ReadDir<'a> {
type Item = Result<DirEntry<'a>, i32>; type Item = IoResult<DirEntry<'a>>;
fn next(&mut self) -> Option<Result<DirEntry<'a>, i32>> { fn next(&mut self) -> Option<IoResult<DirEntry<'a>>> {
unsafe { unsafe {
let mut ret = DirEntry { let mut ret = DirEntry {
entry: mem::zeroed(), entry: mem::zeroed(),
@ -591,7 +640,7 @@ impl<'a> Iterator for ReadDir<'a> {
let r = FSDIR_Read(self.handle.0, &mut entries_read, entry_count, &mut ret.entry); let r = FSDIR_Read(self.handle.0, &mut entries_read, entry_count, &mut ret.entry);
if r < 0 { if r < 0 {
return Some(Err(r)) return Some(Err(IoError::new(IoErrorKind::Other, ::Error::from(r))))
} }
if entries_read != entry_count { if entries_read != entry_count {
return None return None
@ -611,7 +660,7 @@ impl<'a> DirEntry<'a> {
} }
/// Return the metadata for the file that this entry points at. /// Return the metadata for the file that this entry points at.
pub fn metadata(&self) -> Result<Metadata, i32> { pub fn metadata(&self) -> IoResult<Metadata> {
metadata(self.arch, self.path()) metadata(self.arch, self.path())
} }
@ -634,13 +683,13 @@ impl<'a> DirEntry<'a> {
/// but is not limited to just these cases: /// but is not limited to just these cases:
/// ///
/// * User lacks permissions to create directory at `path` /// * User lacks permissions to create directory at `path`
pub fn create_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32> { pub fn create_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
unsafe { unsafe {
let path = to_utf16(path.as_ref()); let path = to_utf16(path.as_ref());
let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
let r = FSUSER_CreateDirectory(arch.handle, fs_path, FS_ATTRIBUTE_DIRECTORY.bits); let r = FSUSER_CreateDirectory(arch.handle, fs_path, FS_ATTRIBUTE_DIRECTORY.bits);
if r < 0 { if r < 0 {
Err(r) Err(IoError::new(IoErrorKind::Other, ::Error::from(r)))
} else { } else {
Ok(()) Ok(())
} }
@ -656,7 +705,7 @@ pub fn create_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32> {
/// ///
/// * If any directory in the path specified by `path` does not already exist /// * If any directory in the path specified by `path` does not already exist
/// and it could not be created otherwise. /// and it could not be created otherwise.
pub fn create_dir_all<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32> { pub fn create_dir_all<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
let path = path.as_ref(); let path = path.as_ref();
let mut dir = PathBuf::new(); let mut dir = PathBuf::new();
let mut result = Ok(()); let mut result = Ok(());
@ -670,13 +719,13 @@ pub fn create_dir_all<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32
} }
/// Given a path, query the file system to get information about a file, directory, etc /// Given a path, query the file system to get information about a file, directory, etc
pub fn metadata<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<Metadata, i32> { pub fn metadata<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<Metadata> {
let maybe_file = File::open(&arch, path.as_ref()); let maybe_file = File::open(&arch, path.as_ref());
let maybe_dir = read_dir(&arch, path.as_ref()); let maybe_dir = read_dir(&arch, path.as_ref());
match (maybe_file, maybe_dir) { match (maybe_file, maybe_dir) {
(Ok(file), _) => file.metadata(), (Ok(file), _) => file.metadata(),
(_, Ok(_dir)) => Ok(Metadata { attributes: FS_ATTRIBUTE_DIRECTORY.bits, size: 0 }), (_, Ok(_dir)) => Ok(Metadata { attributes: FS_ATTRIBUTE_DIRECTORY.bits, size: 0 }),
(Err(r), _) => Err(r), (Err(e), _) => Err(e),
} }
} }
@ -689,13 +738,13 @@ pub fn metadata<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<Metadata, i32
/// ///
/// * The user lacks permissions to remove the directory at the provided path. /// * The user lacks permissions to remove the directory at the provided path.
/// * The directory isn't empty. /// * The directory isn't empty.
pub fn remove_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32> { pub fn remove_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
unsafe { unsafe {
let path = to_utf16(path.as_ref()); let path = to_utf16(path.as_ref());
let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
let r = FSUSER_DeleteDirectory(arch.handle, fs_path); let r = FSUSER_DeleteDirectory(arch.handle, fs_path);
if r < 0 { if r < 0 {
Err(r) Err(IoError::new(IoErrorKind::Other, ::Error::from(r)))
} else { } else {
Ok(()) Ok(())
} }
@ -707,13 +756,13 @@ pub fn remove_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32> {
/// # Errors /// # Errors
/// ///
/// see `file::remove_file` and `fs::remove_dir` /// see `file::remove_file` and `fs::remove_dir`
pub fn remove_dir_all<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32> { pub fn remove_dir_all<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
unsafe { unsafe {
let path = to_utf16(path.as_ref()); let path = to_utf16(path.as_ref());
let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
let r = FSUSER_DeleteDirectoryRecursively(arch.handle, fs_path); let r = FSUSER_DeleteDirectoryRecursively(arch.handle, fs_path);
if r < 0 { if r < 0 {
Err(r) Err(IoError::new(IoErrorKind::Other, ::Error::from(r)))
} else { } else {
Ok(()) Ok(())
} }
@ -731,7 +780,7 @@ pub fn remove_dir_all<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32
/// * The provided path doesn't exist. /// * The provided path doesn't exist.
/// * The process lacks permissions to view the contents. /// * The process lacks permissions to view the contents.
/// * The path points at a non-directory file. /// * The path points at a non-directory file.
pub fn read_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<ReadDir, i32> { pub fn read_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<ReadDir> {
unsafe { unsafe {
let mut handle = 0; let mut handle = 0;
let root = Arc::new(path.as_ref().to_path_buf()); let root = Arc::new(path.as_ref().to_path_buf());
@ -739,7 +788,7 @@ pub fn read_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<ReadDir, i32>
let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
let r = FSUSER_OpenDirectory(&mut handle, arch.handle, fs_path); let r = FSUSER_OpenDirectory(&mut handle, arch.handle, fs_path);
if r < 0 { if r < 0 {
Err(r) Err(IoError::new(IoErrorKind::Other, ::Error::from(r)))
} else { } else {
Ok(ReadDir { handle: Dir(handle), root: root, arch: arch}) Ok(ReadDir { handle: Dir(handle), root: root, arch: arch})
} }
@ -755,13 +804,13 @@ pub fn read_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<ReadDir, i32>
/// ///
/// * path points to a directory. /// * path points to a directory.
/// * The user lacks permissions to remove the file. /// * The user lacks permissions to remove the file.
pub fn remove_file<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32> { pub fn remove_file<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
unsafe { unsafe {
let path = to_utf16(path.as_ref()); let path = to_utf16(path.as_ref());
let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
let r = FSUSER_DeleteFile(arch.handle, fs_path); let r = FSUSER_DeleteFile(arch.handle, fs_path);
if r < 0 { if r < 0 {
Err(r) Err(IoError::new(IoErrorKind::Other, ::Error::from(r)))
} else { } else {
Ok(()) Ok(())
} }
@ -778,7 +827,7 @@ pub fn remove_file<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32> {
/// ///
/// * from does not exist. /// * from does not exist.
/// * The user lacks permissions to view contents. /// * The user lacks permissions to view contents.
pub fn rename<P, Q>(arch: &Archive, from: P, to: Q) -> Result<(), i32> pub fn rename<P, Q>(arch: &Archive, from: P, to: Q) -> IoResult<()>
where P: AsRef<Path>, where P: AsRef<Path>,
Q: AsRef<Path> { Q: AsRef<Path> {
@ -797,7 +846,7 @@ pub fn rename<P, Q>(arch: &Archive, from: P, to: Q) -> Result<(), i32>
if r == 0 { if r == 0 {
return Ok(()) return Ok(())
} }
Err((r)) Err(IoError::new(IoErrorKind::Other, ::Error::from(r)))
} }
} }
@ -815,8 +864,19 @@ fn truncate_utf16_at_nul<'a>(v: &'a [u16]) -> &'a [u16] {
} }
} }
// Adapted from sys/common/io.rs in libstd // Copied from sys/common/io.rs in libstd
unsafe fn read_to_end_uninitialized(f: &mut File, buf: &mut Vec<u8>) -> Result<usize, i32> {
// Provides read_to_end functionality over an uninitialized buffer.
// This function is unsafe because it calls the underlying
// read function with a slice into uninitialized memory. The default
// implementation of read_to_end for readers will zero out new memory in
// the buf before passing it to read, but avoiding this zero can often
// lead to a fairly significant performance win.
//
// Implementations using this method have to adhere to two guarantees:
// * The implementation of read never reads the buffer provided.
// * The implementation of read correctly reports how many bytes were written.
unsafe fn read_to_end_uninitialized(r: &mut Read, buf: &mut Vec<u8>) -> IoResult<usize> {
let start_len = buf.len(); let start_len = buf.len();
buf.reserve(16); buf.reserve(16);
@ -834,14 +894,35 @@ unsafe fn read_to_end_uninitialized(f: &mut File, buf: &mut Vec<u8>) -> Result<u
let buf_slice = slice::from_raw_parts_mut(buf.as_mut_ptr().offset(buf.len() as isize), let buf_slice = slice::from_raw_parts_mut(buf.as_mut_ptr().offset(buf.len() as isize),
buf.capacity() - buf.len()); buf.capacity() - buf.len());
match f.read(buf_slice) { match r.read(buf_slice) {
Ok(0) => { return Ok(buf.len() - start_len); } Ok(0) => { return Ok(buf.len() - start_len); }
Ok(n) => { let len = buf.len() + n; buf.set_len(len); }, Ok(n) => { let len = buf.len() + n; buf.set_len(len); },
Err(ref e) if e.kind() == IoErrorKind::Interrupted => { }
Err(e) => { return Err(e); } Err(e) => { return Err(e); }
} }
} }
} }
impl Read for File {
fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
self.read(buf)
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> IoResult<usize> {
self.read_to_end(buf)
}
}
impl Write for File {
fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
self.write(buf)
}
fn flush(&mut self) -> IoResult<()> {
Ok(())
}
}
impl Drop for Fs { impl Drop for Fs {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {

Loading…
Cancel
Save