Ronald Kinard
8 years ago
committed by
GitHub
14 changed files with 6875 additions and 236 deletions
@ -1,30 +1,32 @@ |
|||||||
{ |
{ |
||||||
"data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64", |
"data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64", |
||||||
"llvm-target": "arm-none-eabihf", |
"llvm-target": "arm-none-eabihf", |
||||||
"linker": "arm-none-eabi-g++", |
"linker": "arm-none-eabi-gcc", |
||||||
"ar": "arm-none-eabi-ar", |
"ar": "arm-none-eabi-ar", |
||||||
"target-endian": "little", |
"target-endian": "little", |
||||||
"target-pointer-width": "32", |
"target-pointer-width": "32", |
||||||
"arch": "arm", |
"arch": "arm", |
||||||
"os": "none", |
"os": "linux", |
||||||
"cpu": "mpcore", |
"cpu": "mpcore", |
||||||
"features": "+vfp2", |
"features": "+vfp2", |
||||||
"relocation-model": "static", |
"relocation-model": "static", |
||||||
"linker-is-gnu": true, |
|
||||||
"has-rpath": true, |
|
||||||
"morestack": false, |
|
||||||
"disable-redzone": true, |
"disable-redzone": true, |
||||||
"executables": true, |
"executables": true, |
||||||
"dynamic-linking": false, |
|
||||||
"no-compiler-rt": true, |
"no-compiler-rt": true, |
||||||
"exe-suffix": ".elf", |
"exe-suffix": ".elf", |
||||||
"is-like-windows": true, |
|
||||||
"function-sections": false, |
|
||||||
"pre-link-args": [ |
"pre-link-args": [ |
||||||
"-specs", |
"-specs=3dsx.specs", |
||||||
"3dsx.specs", |
|
||||||
"-march=armv6k", |
"-march=armv6k", |
||||||
"-mtune=mpcore", |
"-mtune=mpcore", |
||||||
"-mfloat-abi=hard" |
"-mfloat-abi=hard", |
||||||
|
"-mtp=soft" |
||||||
|
], |
||||||
|
"post-link-args": [ |
||||||
|
"-lc", |
||||||
|
"-lm", |
||||||
|
"-lsysbase", |
||||||
|
"-lc", |
||||||
|
"-lgcc", |
||||||
|
"-lc" |
||||||
] |
] |
||||||
} |
} |
||||||
|
@ -0,0 +1,3 @@ |
|||||||
|
pub use self::os_str::{OsString, OsStr}; |
||||||
|
|
||||||
|
mod os_str; |
@ -0,0 +1,576 @@ |
|||||||
|
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
use collections::borrow::{Borrow, Cow, ToOwned}; |
||||||
|
use core::fmt::{self, Debug}; |
||||||
|
use core::mem; |
||||||
|
use collections::String; |
||||||
|
use core::ops; |
||||||
|
use core::cmp; |
||||||
|
use core::hash::{Hash, Hasher}; |
||||||
|
use collections::Vec; |
||||||
|
|
||||||
|
use sys::wtf8::{Wtf8, Wtf8Buf}; |
||||||
|
use sys::{AsInner, IntoInner, FromInner}; |
||||||
|
|
||||||
|
pub use sys::wtf8::EncodeWide; |
||||||
|
|
||||||
|
/// A type that can represent owned, mutable platform-native strings, but is
|
||||||
|
/// cheaply inter-convertible with Rust strings.
|
||||||
|
///
|
||||||
|
/// The need for this type arises from the fact that:
|
||||||
|
///
|
||||||
|
/// * On Unix systems, strings are often arbitrary sequences of non-zero
|
||||||
|
/// bytes, in many cases interpreted as UTF-8.
|
||||||
|
///
|
||||||
|
/// * On Windows, strings are often arbitrary sequences of non-zero 16-bit
|
||||||
|
/// values, interpreted as UTF-16 when it is valid to do so.
|
||||||
|
///
|
||||||
|
/// * In Rust, strings are always valid UTF-8, but may contain zeros.
|
||||||
|
///
|
||||||
|
/// `OsString` and `OsStr` bridge this gap by simultaneously representing Rust
|
||||||
|
/// and platform-native string values, and in particular allowing a Rust string
|
||||||
|
/// to be converted into an "OS" string with no cost.
|
||||||
|
#[derive(Clone)] |
||||||
|
pub struct OsString { |
||||||
|
inner: Wtf8Buf
|
||||||
|
} |
||||||
|
|
||||||
|
/// Slices into OS strings (see `OsString`).
|
||||||
|
pub struct OsStr { |
||||||
|
inner: Wtf8 |
||||||
|
} |
||||||
|
|
||||||
|
impl OsString { |
||||||
|
/// Constructs a new empty `OsString`.
|
||||||
|
pub fn new() -> OsString { |
||||||
|
OsString { inner: Wtf8Buf::from_string(String::new()) } |
||||||
|
} |
||||||
|
|
||||||
|
fn _from_bytes(vec: Vec<u8>) -> Option<OsString> { |
||||||
|
String::from_utf8(vec).ok().map(OsString::from) |
||||||
|
} |
||||||
|
|
||||||
|
/// Converts to an `OsStr` slice.
|
||||||
|
pub fn as_os_str(&self) -> &OsStr { |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Converts the `OsString` into a `String` if it contains valid Unicode data.
|
||||||
|
///
|
||||||
|
/// On failure, ownership of the original `OsString` is returned.
|
||||||
|
pub fn into_string(self) -> Result<String, OsString> { |
||||||
|
self.inner.into_string().map_err(|buf| OsString { inner: buf} ) |
||||||
|
} |
||||||
|
|
||||||
|
/// Extends the string with the given `&OsStr` slice.
|
||||||
|
pub fn push<T: AsRef<OsStr>>(&mut self, s: T) { |
||||||
|
self.inner.push_wtf8(&s.as_ref().inner) |
||||||
|
} |
||||||
|
|
||||||
|
/// Creates a new `OsString` with the given capacity.
|
||||||
|
///
|
||||||
|
/// The string will be able to hold exactly `capacity` lenth units of other
|
||||||
|
/// OS strings without reallocating. If `capacity` is 0, the string will not
|
||||||
|
/// allocate.
|
||||||
|
///
|
||||||
|
/// See main `OsString` documentation information about encoding.
|
||||||
|
pub fn with_capacity(capacity: usize) -> OsString { |
||||||
|
OsString { |
||||||
|
inner: Wtf8Buf::with_capacity(capacity) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Truncates the `OsString` to zero length.
|
||||||
|
pub fn clear(&mut self) { |
||||||
|
self.inner.clear() |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns the capacity this `OsString` can hold without reallocating.
|
||||||
|
///
|
||||||
|
/// See `OsString` introduction for information about encoding.
|
||||||
|
pub fn capacity(&self) -> usize { |
||||||
|
self.inner.capacity() |
||||||
|
} |
||||||
|
|
||||||
|
/// Reserves capacity for at least `additional` more capacity to be inserted
|
||||||
|
/// in the given `OsString`.
|
||||||
|
///
|
||||||
|
/// The collection may reserve more space to avoid frequent reallocations.
|
||||||
|
pub fn reserve(&mut self, additional: usize) { |
||||||
|
self.inner.reserve(additional) |
||||||
|
} |
||||||
|
|
||||||
|
/// Reserves the minimum capacity for exactly `additional` more capacity to
|
||||||
|
/// be inserted in the given `OsString`. Does nothing if the capacity is
|
||||||
|
/// already sufficient.
|
||||||
|
///
|
||||||
|
/// Note that the allocator may give the collection more space than it
|
||||||
|
/// requests. Therefore capacity can not be relied upon to be precisely
|
||||||
|
/// minimal. Prefer reserve if future insertions are expected.
|
||||||
|
pub fn reserve_exact(&mut self, additional: usize) { |
||||||
|
self.inner.reserve_exact(additional) |
||||||
|
} |
||||||
|
|
||||||
|
/// Creates an `OsString` from a potentially ill-formed UTF-16 slice of
|
||||||
|
/// 16-bit code units.
|
||||||
|
///
|
||||||
|
/// This is lossless: calling `.encode_wide()` on the resulting string
|
||||||
|
/// will always return the original code units.
|
||||||
|
///
|
||||||
|
/// NOTE: This function was copied from the windows implementation of OsStringExt
|
||||||
|
pub fn from_wide(wide: &[u16]) -> OsString { |
||||||
|
OsString { inner: Wtf8Buf::from_wide(wide) } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<String> for OsString { |
||||||
|
fn from(s: String) -> OsString { |
||||||
|
OsString { inner: Wtf8Buf::from_string(s) } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a, T: ?Sized + AsRef<OsStr>> From<&'a T> for OsString { |
||||||
|
fn from(s: &'a T) -> OsString { |
||||||
|
s.as_ref().to_os_string() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ops::Index<ops::RangeFull> for OsString { |
||||||
|
type Output = OsStr; |
||||||
|
|
||||||
|
#[inline] |
||||||
|
fn index(&self, _index: ops::RangeFull) -> &OsStr { |
||||||
|
OsStr::from_inner(self.inner.as_slice()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ops::Deref for OsString { |
||||||
|
type Target = OsStr; |
||||||
|
|
||||||
|
#[inline] |
||||||
|
fn deref(&self) -> &OsStr { |
||||||
|
&self[..] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Default for OsString { |
||||||
|
#[inline] |
||||||
|
fn default() -> OsString { |
||||||
|
OsString::new() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Debug for OsString { |
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { |
||||||
|
fmt::Debug::fmt(&**self, formatter) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl PartialEq for OsString { |
||||||
|
fn eq(&self, other: &OsString) -> bool { |
||||||
|
&**self == &**other |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl PartialEq<str> for OsString { |
||||||
|
fn eq(&self, other: &str) -> bool { |
||||||
|
&**self == other |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl PartialEq<OsString> for str { |
||||||
|
fn eq(&self, other: &OsString) -> bool { |
||||||
|
&**other == self |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Eq for OsString {} |
||||||
|
|
||||||
|
impl PartialOrd for OsString { |
||||||
|
#[inline] |
||||||
|
fn partial_cmp(&self, other: &OsString) -> Option<cmp::Ordering> { |
||||||
|
(&**self).partial_cmp(&**other) |
||||||
|
} |
||||||
|
#[inline] |
||||||
|
fn lt(&self, other: &OsString) -> bool { &**self < &**other } |
||||||
|
#[inline] |
||||||
|
fn le(&self, other: &OsString) -> bool { &**self <= &**other } |
||||||
|
#[inline] |
||||||
|
fn gt(&self, other: &OsString) -> bool { &**self > &**other } |
||||||
|
#[inline] |
||||||
|
fn ge(&self, other: &OsString) -> bool { &**self >= &**other } |
||||||
|
} |
||||||
|
|
||||||
|
impl PartialOrd<str> for OsString { |
||||||
|
#[inline] |
||||||
|
fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> { |
||||||
|
(&**self).partial_cmp(other) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Ord for OsString { |
||||||
|
#[inline] |
||||||
|
fn cmp(&self, other: &OsString) -> cmp::Ordering { |
||||||
|
(&**self).cmp(&**other) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Hash for OsString { |
||||||
|
#[inline] |
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) { |
||||||
|
(&**self).hash(state) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl OsStr { |
||||||
|
/// Coerces into an `OsStr` slice.
|
||||||
|
pub fn new<S: AsRef<OsStr> + ?Sized>(s: &S) -> &OsStr { |
||||||
|
s.as_ref() |
||||||
|
} |
||||||
|
|
||||||
|
fn from_inner(inner: &Wtf8) -> &OsStr { |
||||||
|
unsafe { mem::transmute(inner) } |
||||||
|
} |
||||||
|
|
||||||
|
/// Yields a `&str` slice if the `OsStr` is valid Unicode.
|
||||||
|
///
|
||||||
|
/// This conversion may entail doing a check for UTF-8 validity.
|
||||||
|
pub fn to_str(&self) -> Option<&str> { |
||||||
|
self.inner.as_str() |
||||||
|
} |
||||||
|
|
||||||
|
/// Converts an `OsStr` to a `Cow<str>`.
|
||||||
|
///
|
||||||
|
/// Any non-Unicode sequences are replaced with U+FFFD REPLACEMENT CHARACTER.
|
||||||
|
pub fn to_string_lossy(&self) -> Cow<str> { |
||||||
|
self.inner.to_string_lossy() |
||||||
|
} |
||||||
|
|
||||||
|
/// Copies the slice into an owned `OsString`.
|
||||||
|
pub fn to_os_string(&self) -> OsString { |
||||||
|
let mut buf = Wtf8Buf::with_capacity(self.inner.len()); |
||||||
|
buf.push_wtf8(&self.inner); |
||||||
|
OsString { inner: buf } |
||||||
|
} |
||||||
|
|
||||||
|
/// Checks whether the `OsStr` is empty.
|
||||||
|
pub fn is_empty(&self) -> bool { |
||||||
|
self.inner.is_empty() |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns the length of this `OsStr`.
|
||||||
|
///
|
||||||
|
/// Note that this does **not** return the number of bytes in this string
|
||||||
|
/// as, for example, OS strings on Windows are encoded as a list of `u16`
|
||||||
|
/// rather than a list of bytes. This number is simply useful for passing to
|
||||||
|
/// other methods like `OsString::with_capacity` to avoid reallocations.
|
||||||
|
///
|
||||||
|
/// See `OsStr` introduction for more information about encoding.
|
||||||
|
pub fn len(&self) -> usize { |
||||||
|
self.inner.len() |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the underlying byte representation.
|
||||||
|
///
|
||||||
|
/// Note: it is *crucial* that this API is private, to avoid
|
||||||
|
/// revealing the internal, platform-specific encodings.
|
||||||
|
fn bytes(&self) -> &[u8] { |
||||||
|
unsafe { mem::transmute(&self.inner) } |
||||||
|
} |
||||||
|
|
||||||
|
/// Re-encodes an `OsStr` as a wide character sequence,
|
||||||
|
/// i.e. potentially ill-formed UTF-16.
|
||||||
|
/// This is lossless. Note that the encoding does not include a final
|
||||||
|
/// null.
|
||||||
|
///
|
||||||
|
/// NOTE: This function was copied from the windows implementation of OsStrExt
|
||||||
|
pub fn encode_wide(&self) -> EncodeWide { |
||||||
|
self.inner.encode_wide() |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> Default for &'a OsStr { |
||||||
|
#[inline] |
||||||
|
fn default() -> &'a OsStr { |
||||||
|
OsStr::new("") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl PartialEq for OsStr { |
||||||
|
fn eq(&self, other: &OsStr) -> bool { |
||||||
|
self.bytes().eq(other.bytes()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl PartialEq<str> for OsStr { |
||||||
|
fn eq(&self, other: &str) -> bool { |
||||||
|
*self == *OsStr::new(other) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl PartialEq<OsStr> for str { |
||||||
|
fn eq(&self, other: &OsStr) -> bool { |
||||||
|
*other == *OsStr::new(self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Eq for OsStr {} |
||||||
|
|
||||||
|
impl PartialOrd for OsStr { |
||||||
|
#[inline] |
||||||
|
fn partial_cmp(&self, other: &OsStr) -> Option<cmp::Ordering> { |
||||||
|
self.bytes().partial_cmp(other.bytes()) |
||||||
|
} |
||||||
|
#[inline] |
||||||
|
fn lt(&self, other: &OsStr) -> bool { self.bytes().lt(other.bytes()) } |
||||||
|
#[inline] |
||||||
|
fn le(&self, other: &OsStr) -> bool { self.bytes().le(other.bytes()) } |
||||||
|
#[inline] |
||||||
|
fn gt(&self, other: &OsStr) -> bool { self.bytes().gt(other.bytes()) } |
||||||
|
#[inline] |
||||||
|
fn ge(&self, other: &OsStr) -> bool { self.bytes().ge(other.bytes()) } |
||||||
|
} |
||||||
|
|
||||||
|
impl PartialOrd<str> for OsStr { |
||||||
|
#[inline] |
||||||
|
fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> { |
||||||
|
self.partial_cmp(OsStr::new(other)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// FIXME (#19470): cannot provide PartialOrd<OsStr> for str until we
|
||||||
|
// have more flexible coherence rules.
|
||||||
|
|
||||||
|
impl Ord for OsStr { |
||||||
|
#[inline] |
||||||
|
fn cmp(&self, other: &OsStr) -> cmp::Ordering { self.bytes().cmp(other.bytes()) } |
||||||
|
} |
||||||
|
|
||||||
|
macro_rules! impl_cmp { |
||||||
|
($lhs:ty, $rhs: ty) => { |
||||||
|
impl<'a, 'b> PartialEq<$rhs> for $lhs { |
||||||
|
#[inline] |
||||||
|
fn eq(&self, other: &$rhs) -> bool { <OsStr as PartialEq>::eq(self, other) } |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a, 'b> PartialEq<$lhs> for $rhs { |
||||||
|
#[inline] |
||||||
|
fn eq(&self, other: &$lhs) -> bool { <OsStr as PartialEq>::eq(self, other) } |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a, 'b> PartialOrd<$rhs> for $lhs { |
||||||
|
#[inline] |
||||||
|
fn partial_cmp(&self, other: &$rhs) -> Option<cmp::Ordering> { |
||||||
|
<OsStr as PartialOrd>::partial_cmp(self, other) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a, 'b> PartialOrd<$lhs> for $rhs { |
||||||
|
#[inline] |
||||||
|
fn partial_cmp(&self, other: &$lhs) -> Option<cmp::Ordering> { |
||||||
|
<OsStr as PartialOrd>::partial_cmp(self, other) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl_cmp!(OsString, OsStr); |
||||||
|
impl_cmp!(OsString, &'a OsStr); |
||||||
|
impl_cmp!(Cow<'a, OsStr>, OsStr); |
||||||
|
impl_cmp!(Cow<'a, OsStr>, &'b OsStr); |
||||||
|
impl_cmp!(Cow<'a, OsStr>, OsString); |
||||||
|
|
||||||
|
impl Hash for OsStr { |
||||||
|
#[inline] |
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) { |
||||||
|
self.bytes().hash(state) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Debug for OsStr { |
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { |
||||||
|
self.inner.fmt(formatter) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Borrow<OsStr> for OsString { |
||||||
|
fn borrow(&self) -> &OsStr { &self[..] } |
||||||
|
} |
||||||
|
|
||||||
|
impl ToOwned for OsStr { |
||||||
|
type Owned = OsString; |
||||||
|
fn to_owned(&self) -> OsString { self.to_os_string() } |
||||||
|
} |
||||||
|
|
||||||
|
impl AsRef<OsStr> for OsStr { |
||||||
|
fn as_ref(&self) -> &OsStr { |
||||||
|
self |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl AsRef<OsStr> for OsString { |
||||||
|
fn as_ref(&self) -> &OsStr { |
||||||
|
self |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl AsRef<OsStr> for str { |
||||||
|
fn as_ref(&self) -> &OsStr { |
||||||
|
OsStr::from_inner(Wtf8::from_str(self)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl AsRef<OsStr> for String { |
||||||
|
fn as_ref(&self) -> &OsStr { |
||||||
|
(&**self).as_ref() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl FromInner<Wtf8Buf> for OsString { |
||||||
|
fn from_inner(buf: Wtf8Buf) -> OsString { |
||||||
|
OsString { inner: buf } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl IntoInner<Wtf8Buf> for OsString { |
||||||
|
fn into_inner(self) -> Wtf8Buf { |
||||||
|
self.inner |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl AsInner<Wtf8> for OsStr { |
||||||
|
fn as_inner(&self) -> &Wtf8 { |
||||||
|
&self.inner |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
use super::*; |
||||||
|
use sys::{AsInner, IntoInner}; |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_os_string_with_capacity() { |
||||||
|
let os_string = OsString::with_capacity(0); |
||||||
|
assert_eq!(0, os_string.inner.capacity()); |
||||||
|
|
||||||
|
let os_string = OsString::with_capacity(10); |
||||||
|
assert_eq!(10, os_string.inner.capacity()); |
||||||
|
|
||||||
|
let mut os_string = OsString::with_capacity(0); |
||||||
|
os_string.push("abc"); |
||||||
|
assert!(os_string.inner.capacity() >= 3); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_os_string_clear() { |
||||||
|
let mut os_string = OsString::from("abc"); |
||||||
|
assert_eq!(3, os_string.inner.len()); |
||||||
|
|
||||||
|
os_string.clear(); |
||||||
|
assert_eq!(&os_string, ""); |
||||||
|
assert_eq!(0, os_string.inner.len()); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_os_string_capacity() { |
||||||
|
let os_string = OsString::with_capacity(0); |
||||||
|
assert_eq!(0, os_string.capacity()); |
||||||
|
|
||||||
|
let os_string = OsString::with_capacity(10); |
||||||
|
assert_eq!(10, os_string.capacity()); |
||||||
|
|
||||||
|
let mut os_string = OsString::with_capacity(0); |
||||||
|
os_string.push("abc"); |
||||||
|
assert!(os_string.capacity() >= 3); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_os_string_reserve() { |
||||||
|
let mut os_string = OsString::new(); |
||||||
|
assert_eq!(os_string.capacity(), 0); |
||||||
|
|
||||||
|
os_string.reserve(2); |
||||||
|
assert!(os_string.capacity() >= 2); |
||||||
|
|
||||||
|
for _ in 0..16 { |
||||||
|
os_string.push("a"); |
||||||
|
} |
||||||
|
|
||||||
|
assert!(os_string.capacity() >= 16); |
||||||
|
os_string.reserve(16); |
||||||
|
assert!(os_string.capacity() >= 32); |
||||||
|
|
||||||
|
os_string.push("a"); |
||||||
|
|
||||||
|
os_string.reserve(16); |
||||||
|
assert!(os_string.capacity() >= 33) |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_os_string_reserve_exact() { |
||||||
|
let mut os_string = OsString::new(); |
||||||
|
assert_eq!(os_string.capacity(), 0); |
||||||
|
|
||||||
|
os_string.reserve_exact(2); |
||||||
|
assert!(os_string.capacity() >= 2); |
||||||
|
|
||||||
|
for _ in 0..16 { |
||||||
|
os_string.push("a"); |
||||||
|
} |
||||||
|
|
||||||
|
assert!(os_string.capacity() >= 16); |
||||||
|
os_string.reserve_exact(16); |
||||||
|
assert!(os_string.capacity() >= 32); |
||||||
|
|
||||||
|
os_string.push("a"); |
||||||
|
|
||||||
|
os_string.reserve_exact(16); |
||||||
|
assert!(os_string.capacity() >= 33) |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_os_string_default() { |
||||||
|
let os_string: OsString = Default::default(); |
||||||
|
assert_eq!("", &os_string); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_os_str_is_empty() { |
||||||
|
let mut os_string = OsString::new(); |
||||||
|
assert!(os_string.is_empty()); |
||||||
|
|
||||||
|
os_string.push("abc"); |
||||||
|
assert!(!os_string.is_empty()); |
||||||
|
|
||||||
|
os_string.clear(); |
||||||
|
assert!(os_string.is_empty()); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_os_str_len() { |
||||||
|
let mut os_string = OsString::new(); |
||||||
|
assert_eq!(0, os_string.len()); |
||||||
|
|
||||||
|
os_string.push("abc"); |
||||||
|
assert_eq!(3, os_string.len()); |
||||||
|
|
||||||
|
os_string.clear(); |
||||||
|
assert_eq!(0, os_string.len()); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_os_str_default() { |
||||||
|
let os_str: &OsStr = Default::default(); |
||||||
|
assert_eq!("", os_str); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,892 @@ |
|||||||
|
//! Filesystem service
|
||||||
|
//!
|
||||||
|
//! This module contains basic methods to manipulate the contents of the 3DS's filesystem.
|
||||||
|
//! Only the SD card is currently supported.
|
||||||
|
|
||||||
|
|
||||||
|
use core::marker::PhantomData; |
||||||
|
use core::ptr; |
||||||
|
use core::slice; |
||||||
|
use core::mem; |
||||||
|
use alloc::arc::Arc; |
||||||
|
use collections::Vec; |
||||||
|
|
||||||
|
use path::{Path, PathBuf}; |
||||||
|
use ffi::OsString; |
||||||
|
|
||||||
|
use libctru::services::fs::*; |
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)] |
||||||
|
pub enum PathType { |
||||||
|
Invalid, |
||||||
|
Empty, |
||||||
|
Binary, |
||||||
|
ASCII, |
||||||
|
UTF16, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)] |
||||||
|
pub enum ArchiveID { |
||||||
|
RomFS, |
||||||
|
Savedata, |
||||||
|
Extdata, |
||||||
|
SharedExtdata, |
||||||
|
SystemSavedata, |
||||||
|
Sdmc, |
||||||
|
SdmcWriteOnly, |
||||||
|
BossExtdata, |
||||||
|
CardSpiFS, |
||||||
|
ExtDataAndBossExtdata, |
||||||
|
SystemSaveData2, |
||||||
|
NandRW, |
||||||
|
NandRO, |
||||||
|
NandROWriteAccess, |
||||||
|
SaveDataAndContent, |
||||||
|
SaveDataAndContent2, |
||||||
|
NandCtrFS, |
||||||
|
TwlPhoto, |
||||||
|
NandTwlFS, |
||||||
|
GameCardSavedata, |
||||||
|
UserSavedata, |
||||||
|
DemoSavedata, |
||||||
|
} |
||||||
|
|
||||||
|
/// Represents the filesystem service. No file IO can be performed
|
||||||
|
/// until an instance of this struct is created.
|
||||||
|
///
|
||||||
|
/// The service exits when all instances of this struct go out of scope.
|
||||||
|
pub struct Fs { |
||||||
|
pd: PhantomData<i32>, |
||||||
|
} |
||||||
|
|
||||||
|
/// Handle to an open filesystem archive.
|
||||||
|
///
|
||||||
|
/// Archives are automatically closed when they go out of scope.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use ctru::services::fs::Fs
|
||||||
|
///
|
||||||
|
/// let fs = Fs::init().unwrap();
|
||||||
|
/// let sdmc_archive = fs.sdmc().unwrap();
|
||||||
|
/// ```
|
||||||
|
pub struct Archive { |
||||||
|
id: ArchiveID, |
||||||
|
handle: u64, |
||||||
|
} |
||||||
|
|
||||||
|
/// A reference to an open file on the filesystem.
|
||||||
|
///
|
||||||
|
/// An instance of a `File` can be read and/or written to depending
|
||||||
|
/// on what options It was opened with.
|
||||||
|
///
|
||||||
|
/// Files are automatically closed when they go out of scope.
|
||||||
|
pub struct File { |
||||||
|
handle: u32, |
||||||
|
offset: u64, |
||||||
|
} |
||||||
|
|
||||||
|
/// Metadata information about a file.
|
||||||
|
///
|
||||||
|
/// This structure is returned from the [`metadata`] function and
|
||||||
|
/// represents known metadata about a file.
|
||||||
|
///
|
||||||
|
/// [`metadata`]: fn.metadata.html
|
||||||
|
pub struct Metadata { |
||||||
|
attributes: u32, |
||||||
|
size: u64, |
||||||
|
} |
||||||
|
|
||||||
|
/// Options and flags which can be used to configure how a [`File`] is opened.
|
||||||
|
/// This builder exposes the ability to configure how a `File` is opened
|
||||||
|
/// and what operations are permitted on the open file. The [`File::open`]
|
||||||
|
/// and [`File::create`] methods are aliases for commonly used options
|
||||||
|
/// using this builder.
|
||||||
|
///
|
||||||
|
/// [`File`]: struct.File.html
|
||||||
|
/// [`File::open`]: struct.File.html#method.open
|
||||||
|
/// [`File::create`]: struct.File.html#method.create
|
||||||
|
///
|
||||||
|
/// Generally speaking, when using `OpenOptions`, you'll first call [`new()`],
|
||||||
|
/// then chain calls to methods to set each option, then call [`open()`],
|
||||||
|
/// passing the path of the file you're trying to open.
|
||||||
|
///
|
||||||
|
/// It is required to also pass a reference to the [`Archive`] that the
|
||||||
|
/// file lives in.
|
||||||
|
///
|
||||||
|
/// [`new()`]: struct.OpenOptions.html#method.new
|
||||||
|
/// [`open()`]: struct.OpenOptions.html#method.open
|
||||||
|
/// [`Archive`]: struct.Archive.html
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Opening a file to read:
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use ctru::services::fs::OpenOptions;
|
||||||
|
///
|
||||||
|
/// let fs = Fs::init().unwrap();
|
||||||
|
/// let sdmc_archive = fs.sdmc().unwrap();
|
||||||
|
/// let file = OpenOptions::new()
|
||||||
|
/// .read(true)
|
||||||
|
/// .archive(&sdmc_archive)
|
||||||
|
/// .open("foo.txt")
|
||||||
|
/// .unwrap();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Opening a file for both reading and writing, as well as creating it if it
|
||||||
|
/// doesn't exist:
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use ctru::services::fs::OpenOptions;
|
||||||
|
///
|
||||||
|
/// let fs = Fs::init().unwrap();
|
||||||
|
/// let sdmc_archive = fs.sdmc().unwrap();
|
||||||
|
/// let file = OpenOptions::new()
|
||||||
|
/// .read(true)
|
||||||
|
/// .write(true)
|
||||||
|
/// .create(true)
|
||||||
|
/// .archive(&sdmc_archive)
|
||||||
|
/// .open("foo.txt")
|
||||||
|
/// .unwrap();
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone)] |
||||||
|
pub struct OpenOptions { |
||||||
|
read: bool, |
||||||
|
write: bool, |
||||||
|
append: bool, |
||||||
|
truncate: bool, |
||||||
|
create: bool, |
||||||
|
arch_handle: u64, |
||||||
|
} |
||||||
|
|
||||||
|
/// Iterator over the entries in a directory.
|
||||||
|
///
|
||||||
|
/// This iterator is returned from the [`read_dir`] function of this module and
|
||||||
|
/// will yield instances of `Result<DirEntry, i32>`. Through a [`DirEntry`]
|
||||||
|
/// information like the entry's path and possibly other metadata can be
|
||||||
|
/// learned.
|
||||||
|
///
|
||||||
|
/// [`read_dir`]: fn.read_dir.html
|
||||||
|
/// [`DirEntry`]: struct.DirEntry.html
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This Result will return Err if there's some sort of intermittent IO error
|
||||||
|
/// during iteration.
|
||||||
|
pub struct ReadDir<'a> { |
||||||
|
handle: Dir, |
||||||
|
root: Arc<PathBuf>, |
||||||
|
arch: &'a Archive, |
||||||
|
} |
||||||
|
|
||||||
|
/// Entries returned by the [`ReadDir`] iterator.
|
||||||
|
///
|
||||||
|
/// [`ReadDir`]: struct.ReadDir.html
|
||||||
|
///
|
||||||
|
/// An instance of `DirEntry` represents an entry inside of a directory on the
|
||||||
|
/// filesystem. Each entry can be inspected via methods to learn about the full
|
||||||
|
/// path or possibly other metadata.
|
||||||
|
pub struct DirEntry<'a> { |
||||||
|
entry: FS_DirectoryEntry, |
||||||
|
root: Arc<PathBuf>, |
||||||
|
arch: &'a Archive, |
||||||
|
} |
||||||
|
|
||||||
|
#[doc(hidden)] |
||||||
|
struct Dir(u32); |
||||||
|
|
||||||
|
#[doc(hidden)] |
||||||
|
unsafe impl Send for Dir {} |
||||||
|
#[doc(hidden)] |
||||||
|
unsafe impl Sync for Dir {} |
||||||
|
|
||||||
|
impl Fs { |
||||||
|
/// Initializes the FS service.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return Err if there was an error initializing the
|
||||||
|
/// FS service, which in practice should never happen unless there is
|
||||||
|
/// an error in the execution environment (i.e. the homebrew launcher
|
||||||
|
/// somehow fails to provide fs:USER permissions)
|
||||||
|
///
|
||||||
|
/// ctrulib services are reference counted, so this function may be called
|
||||||
|
/// as many times as desired and the service will not exit until all
|
||||||
|
/// instances of Fs drop out of scope.
|
||||||
|
pub fn init() -> Result<Fs, i32> { |
||||||
|
unsafe { |
||||||
|
let r = fsInit(); |
||||||
|
if r < 0 { |
||||||
|
Err(r) |
||||||
|
} else { |
||||||
|
Ok(Fs { pd: PhantomData }) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns a handle to the SDMC (memory card) Archive.
|
||||||
|
pub fn sdmc(&self) -> Result<Archive, i32> { |
||||||
|
unsafe { |
||||||
|
let mut handle = 0; |
||||||
|
let id = ArchiveID::Sdmc; |
||||||
|
let path = fsMakePath(PathType::Empty.into(), ptr::null() as _); |
||||||
|
let r = FSUSER_OpenArchive(&mut handle, id.into(), path); |
||||||
|
if r < 0 { |
||||||
|
Err(r) |
||||||
|
} else { |
||||||
|
Ok(Archive { |
||||||
|
handle: handle, |
||||||
|
id: id, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Archive { |
||||||
|
/// Retrieves an Archive's [`ArchiveID`]
|
||||||
|
///
|
||||||
|
/// [`ArchiveID`]: enum.ArchiveID.html
|
||||||
|
pub fn get_id(&self) -> ArchiveID { |
||||||
|
self.id |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl File { |
||||||
|
/// Attempts to open a file in read-only mode.
|
||||||
|
///
|
||||||
|
/// See the [`OpenOptions::open`] method for more details.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if `path` does not already exit.
|
||||||
|
/// Other errors may also be returned accoridng to [`OpenOptions::open`]
|
||||||
|
///
|
||||||
|
/// [`OpenOptions::open`]: struct.OpenOptions.html#method.open
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use ctru::services::fs::{Fs, File};
|
||||||
|
///
|
||||||
|
/// let fs = Fs::init().unwrap()
|
||||||
|
/// let sdmc_archive = fs.sdmc().unwrap()
|
||||||
|
/// let mut f = File::open(&sdmc_archive, "/foo.txt").unwrap();
|
||||||
|
/// ```
|
||||||
|
pub fn open<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<File, i32> { |
||||||
|
OpenOptions::new().read(true).archive(arch).open(path.as_ref()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Opens a file in write-only mode.
|
||||||
|
///
|
||||||
|
/// This function will create a file if it does not exist.
|
||||||
|
///
|
||||||
|
/// See the [`OpenOptions::create`] method for more details.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if `path` does not already exit.
|
||||||
|
/// Other errors may also be returned accoridng to [`OpenOptions::create`]
|
||||||
|
///
|
||||||
|
/// [`OpenOptions::create`]: struct.OpenOptions.html#method.create
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use ctru::services::fs::{Fs, File};
|
||||||
|
///
|
||||||
|
/// let fs = Fs::init().unwrap()
|
||||||
|
/// let sdmc_archive = fs.sdmc().unwrap()
|
||||||
|
/// let mut f = File::create(&sdmc_archive, "/foo.txt").unwrap();
|
||||||
|
/// ```
|
||||||
|
pub fn create<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<File, i32> { |
||||||
|
OpenOptions::new().write(true).create(true).archive(arch).open(path.as_ref()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Truncates or extends the underlying file, updating the size of this file to become size.
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
/// of the intermediate data filled in with 0s.
|
||||||
|
pub fn set_len(&mut self, size: u64) -> Result<(), i32> { |
||||||
|
unsafe { |
||||||
|
let r = FSFILE_SetSize(self.handle, size); |
||||||
|
if r < 0 { |
||||||
|
Err(r) |
||||||
|
} else { |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Queries metadata about the underlying file.
|
||||||
|
pub fn metadata(&self) -> Result<Metadata, i32> { |
||||||
|
// The only metadata we have for files right now is file size.
|
||||||
|
// This is likely to change in the future.
|
||||||
|
unsafe { |
||||||
|
let mut size = 0; |
||||||
|
let r = FSFILE_GetSize(self.handle, &mut size); |
||||||
|
if r < 0 { |
||||||
|
Err(r) |
||||||
|
} else { |
||||||
|
Ok(Metadata { attributes: 0, size: size }) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Pull some bytes from the file into the specified buffer, returning
|
||||||
|
/// 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 { |
||||||
|
let mut n_read = 0; |
||||||
|
let r = FSFILE_Read( |
||||||
|
self.handle, |
||||||
|
&mut n_read, |
||||||
|
self.offset, |
||||||
|
buf.as_mut_ptr() as _, |
||||||
|
buf.len() as u32 |
||||||
|
); |
||||||
|
self.offset += n_read as u64; |
||||||
|
if r < 0 { |
||||||
|
Err(r) |
||||||
|
} else { |
||||||
|
Ok(n_read as usize) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Read all bytes until EOF in this source, placing them into 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.
|
||||||
|
///
|
||||||
|
/// 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 { |
||||||
|
let mut n_written = 0; |
||||||
|
let r = FSFILE_Write( |
||||||
|
self.handle, |
||||||
|
&mut n_written, |
||||||
|
self.offset, |
||||||
|
buf.as_ptr() as _, |
||||||
|
buf.len() as u32, |
||||||
|
FS_WRITE_UPDATE_TIME |
||||||
|
); |
||||||
|
self.offset += n_written as u64; |
||||||
|
if r < 0 { |
||||||
|
Err(r) |
||||||
|
} else { |
||||||
|
Ok(n_written as usize) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Metadata { |
||||||
|
/// Returns whether this metadata is for a directory.
|
||||||
|
pub fn is_dir(&self) -> bool { |
||||||
|
self.attributes == self.attributes | FS_ATTRIBUTE_DIRECTORY |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns whether this metadata is for a regular file.
|
||||||
|
pub fn is_file(&self) -> bool { |
||||||
|
!self.is_dir() |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns the size, in bytes, this metadata is for.
|
||||||
|
///
|
||||||
|
/// Directories return size = 0.
|
||||||
|
pub fn len(&self) -> u64 { |
||||||
|
self.size |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl OpenOptions { |
||||||
|
/// Creates a blank set of options ready for configuration.
|
||||||
|
///
|
||||||
|
/// All options are initially set to `false`
|
||||||
|
pub fn new() -> OpenOptions { |
||||||
|
OpenOptions { |
||||||
|
read: false, |
||||||
|
write: false, |
||||||
|
append: false, |
||||||
|
truncate: false, |
||||||
|
create: false, |
||||||
|
arch_handle: 0, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Sets the option for read access.
|
||||||
|
///
|
||||||
|
/// This option, when true, will indicate that the file should be
|
||||||
|
/// `read`-able if opened.
|
||||||
|
pub fn read(&mut self, read: bool) -> &mut OpenOptions { |
||||||
|
self.read = read; |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Sets the option for write access.
|
||||||
|
///
|
||||||
|
/// This option, when true, will indicate that the file should be
|
||||||
|
/// `write`-able if opened.
|
||||||
|
///
|
||||||
|
/// If the file already exists, any write calls on it will overwrite
|
||||||
|
/// its contents, without truncating it.
|
||||||
|
pub fn write(&mut self, write: bool) -> &mut OpenOptions { |
||||||
|
self.write = write; |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Sets the option for the append mode.
|
||||||
|
///
|
||||||
|
/// This option, when true, means that writes will append to a file instead
|
||||||
|
/// of overwriting previous contents. Note that setting .write(true).append(true)
|
||||||
|
/// has the same effect as setting only .append(true).
|
||||||
|
///
|
||||||
|
/// If both truncate and append are set to true, the file will simply be truncated
|
||||||
|
pub fn append(&mut self, append: bool) -> &mut OpenOptions { |
||||||
|
self.append = append; |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Sets the option for truncating a previous file.
|
||||||
|
///
|
||||||
|
/// If a file is successfully opened with this option set it will truncate
|
||||||
|
/// the file to 0 length if it already exists.
|
||||||
|
///
|
||||||
|
/// The file must be opened with write access for truncate to work.
|
||||||
|
pub fn truncate(&mut self, truncate: bool) -> &mut OpenOptions { |
||||||
|
self.truncate = truncate; |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Sets the option for creating a new file.
|
||||||
|
///
|
||||||
|
/// This option indicates whether a new file will be created
|
||||||
|
/// if the file does not yet already
|
||||||
|
/// exist.
|
||||||
|
///
|
||||||
|
/// In order for the file to be created, write access must also be used.
|
||||||
|
pub fn create(&mut self, create: bool) -> &mut OpenOptions { |
||||||
|
self.create = create; |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Sets which archive the file is to be opened in.
|
||||||
|
///
|
||||||
|
/// Failing to pass in an archive will result in the file failing to open.
|
||||||
|
pub fn archive(&mut self, archive: &Archive) -> &mut OpenOptions { |
||||||
|
self.arch_handle = archive.handle; |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Opens a file at `path` with the options specified by `self`
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error under a number of different
|
||||||
|
/// circumstances, including but not limited to:
|
||||||
|
///
|
||||||
|
/// * Opening a file that doesn't exist without setting `create`.
|
||||||
|
/// * Attempting to open a file without passing an [`Archive`] reference
|
||||||
|
/// to the `archive` method.
|
||||||
|
/// * Filesystem-level errors (full disk, etc).
|
||||||
|
/// * Invalid combinations of open options.
|
||||||
|
///
|
||||||
|
/// [`Archive`]: struct.Archive.html
|
||||||
|
pub fn open<P: AsRef<Path>>(&self, path: P) -> Result<File, i32> { |
||||||
|
self._open(path.as_ref(), self.get_open_flags()) |
||||||
|
} |
||||||
|
|
||||||
|
fn _open(&self, path: &Path, flags: u32) -> Result<File, i32> { |
||||||
|
unsafe { |
||||||
|
let mut file_handle = 0; |
||||||
|
let path = to_utf16(path); |
||||||
|
let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); |
||||||
|
let r = FSUSER_OpenFile(&mut file_handle, self.arch_handle, fs_path, flags, 0); |
||||||
|
if r < 0 { |
||||||
|
return Err(r); |
||||||
|
} |
||||||
|
|
||||||
|
let mut file = File { handle: file_handle, offset: 0 }; |
||||||
|
|
||||||
|
if self.append { |
||||||
|
let metadata = try!(file.metadata()); |
||||||
|
file.offset = metadata.len(); |
||||||
|
} |
||||||
|
|
||||||
|
// set the offset to 0 just in case both append and truncate were
|
||||||
|
// set to true
|
||||||
|
if self.truncate { |
||||||
|
try!(file.set_len(0)); |
||||||
|
file.offset = 0; |
||||||
|
} |
||||||
|
Ok(file) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn get_open_flags(&self) -> u32 { |
||||||
|
match (self.read, self.write || self.append, self.create) { |
||||||
|
(true, false, false) => FS_OPEN_READ, |
||||||
|
(false, true, false) => FS_OPEN_WRITE, |
||||||
|
(false, true, true) => FS_OPEN_WRITE | FS_OPEN_CREATE, |
||||||
|
(true, false, true) => FS_OPEN_READ | FS_OPEN_CREATE, |
||||||
|
(true, true, false) => FS_OPEN_READ | FS_OPEN_WRITE, |
||||||
|
(true, true, true) => FS_OPEN_READ | FS_OPEN_WRITE | FS_OPEN_CREATE, |
||||||
|
_ => 0, //failure case
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> Iterator for ReadDir<'a> { |
||||||
|
type Item = Result<DirEntry<'a>, i32>; |
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Result<DirEntry<'a>, i32>> { |
||||||
|
unsafe { |
||||||
|
let mut ret = DirEntry { |
||||||
|
entry: mem::zeroed(), |
||||||
|
root: self.root.clone(), |
||||||
|
arch: self.arch, |
||||||
|
}; |
||||||
|
let mut entries_read = 0; |
||||||
|
let entry_count = 1; |
||||||
|
let r = FSDIR_Read(self.handle.0, &mut entries_read, entry_count, &mut ret.entry); |
||||||
|
|
||||||
|
if r < 0 { |
||||||
|
return Some(Err(r)) |
||||||
|
} |
||||||
|
if entries_read != entry_count { |
||||||
|
return None |
||||||
|
} |
||||||
|
Some(Ok(ret)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> DirEntry<'a> { |
||||||
|
/// Returns the full path to the file that this entry represents.
|
||||||
|
///
|
||||||
|
/// The full path is created by joining the original path to `read_dir`
|
||||||
|
/// with the filename of this entry.
|
||||||
|
pub fn path(&self) -> PathBuf { |
||||||
|
self.root.join(&self.file_name()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Return the metadata for the file that this entry points at.
|
||||||
|
pub fn metadata(&self) -> Result<Metadata, i32> { |
||||||
|
metadata(self.arch, self.path()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns the bare file name of this directory entry without any other leading path
|
||||||
|
/// component.
|
||||||
|
pub fn file_name(&self) -> OsString { |
||||||
|
let filename = truncate_utf16_at_nul(&self.entry.name); |
||||||
|
OsString::from_wide(filename) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Creates a new, empty directory at the provided path
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error in the following situations,
|
||||||
|
/// but is not limited to just these cases:
|
||||||
|
///
|
||||||
|
/// * User lacks permissions to create directory at `path`
|
||||||
|
pub fn create_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32> { |
||||||
|
unsafe { |
||||||
|
let path = to_utf16(path.as_ref()); |
||||||
|
let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); |
||||||
|
let r = FSUSER_CreateDirectory(arch.handle, fs_path, FS_ATTRIBUTE_DIRECTORY); |
||||||
|
if r < 0 { |
||||||
|
Err(r) |
||||||
|
} else { |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Recursively create a directory and all of its parent components if they are missing.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error in the following situations,
|
||||||
|
/// but is not limited to just these cases:
|
||||||
|
///
|
||||||
|
/// * If any directory in the path specified by `path` does not already exist
|
||||||
|
/// and it could not be created otherwise.
|
||||||
|
pub fn create_dir_all<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32> { |
||||||
|
let path = path.as_ref(); |
||||||
|
let mut dir = PathBuf::new(); |
||||||
|
let mut result = Ok(()); |
||||||
|
|
||||||
|
for component in path.components() { |
||||||
|
let component = component.as_os_str(); |
||||||
|
dir.push(component); |
||||||
|
result = create_dir(arch, &dir); |
||||||
|
} |
||||||
|
result |
||||||
|
} |
||||||
|
|
||||||
|
/// 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> { |
||||||
|
let maybe_file = File::open(&arch, path.as_ref()); |
||||||
|
let maybe_dir = read_dir(&arch, path.as_ref()); |
||||||
|
match (maybe_file, maybe_dir) { |
||||||
|
(Ok(file), _) => file.metadata(), |
||||||
|
(_, Ok(_dir)) => Ok(Metadata { attributes: FS_ATTRIBUTE_DIRECTORY, size: 0 }), |
||||||
|
(Err(r), _) => Err(r), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Removes an existing, empty directory.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error in the following situations, but is not limited to just
|
||||||
|
/// these cases:
|
||||||
|
///
|
||||||
|
/// * The user lacks permissions to remove the directory at the provided path.
|
||||||
|
/// * The directory isn't empty.
|
||||||
|
pub fn remove_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32> { |
||||||
|
unsafe { |
||||||
|
let path = to_utf16(path.as_ref()); |
||||||
|
let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); |
||||||
|
let r = FSUSER_DeleteDirectory(arch.handle, fs_path); |
||||||
|
if r < 0 { |
||||||
|
Err(r) |
||||||
|
} else { |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Removes a directory at this path, after removing all its contents. Use carefully!
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// see `file::remove_file` and `fs::remove_dir`
|
||||||
|
pub fn remove_dir_all<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32> { |
||||||
|
unsafe { |
||||||
|
let path = to_utf16(path.as_ref()); |
||||||
|
let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); |
||||||
|
let r = FSUSER_DeleteDirectoryRecursively(arch.handle, fs_path); |
||||||
|
if r < 0 { |
||||||
|
Err(r) |
||||||
|
} else { |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns an iterator over the entries within a directory.
|
||||||
|
///
|
||||||
|
/// The iterator will yield instances of Result<DirEntry, i32>. New errors
|
||||||
|
/// may be encountered after an iterator is initially constructed.
|
||||||
|
///
|
||||||
|
/// This function will return an error in the following situations, but is not limited to just
|
||||||
|
/// these cases:
|
||||||
|
///
|
||||||
|
/// * The provided path doesn't exist.
|
||||||
|
/// * The process lacks permissions to view the contents.
|
||||||
|
/// * The path points at a non-directory file.
|
||||||
|
pub fn read_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<ReadDir, i32> { |
||||||
|
unsafe { |
||||||
|
let mut handle = 0; |
||||||
|
let root = Arc::new(path.as_ref().to_path_buf()); |
||||||
|
let path = to_utf16(path.as_ref()); |
||||||
|
let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); |
||||||
|
let r = FSUSER_OpenDirectory(&mut handle, arch.handle, fs_path); |
||||||
|
if r < 0 { |
||||||
|
Err(r) |
||||||
|
} else { |
||||||
|
Ok(ReadDir { handle: Dir(handle), root: root, arch: arch}) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Removes a file from the filesystem.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error in the following situations, but is not limited to just
|
||||||
|
/// these cases:
|
||||||
|
///
|
||||||
|
/// * path points to a directory.
|
||||||
|
/// * The user lacks permissions to remove the file.
|
||||||
|
pub fn remove_file<P: AsRef<Path>>(arch: &Archive, path: P) -> Result<(), i32> { |
||||||
|
unsafe { |
||||||
|
let path = to_utf16(path.as_ref()); |
||||||
|
let fs_path = fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); |
||||||
|
let r = FSUSER_DeleteFile(arch.handle, fs_path); |
||||||
|
if r < 0 { |
||||||
|
Err(r) |
||||||
|
} else { |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Rename a file or directory to a new name, replacing the original file
|
||||||
|
/// if to already exists.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error in the following situations, but is not limited to just
|
||||||
|
/// these cases:
|
||||||
|
///
|
||||||
|
/// * from does not exist.
|
||||||
|
/// * The user lacks permissions to view contents.
|
||||||
|
pub fn rename<P, Q>(arch: &Archive, from: P, to: Q) -> Result<(), i32> |
||||||
|
where P: AsRef<Path>, |
||||||
|
Q: AsRef<Path> { |
||||||
|
|
||||||
|
unsafe { |
||||||
|
let from = to_utf16(from.as_ref()); |
||||||
|
let to = to_utf16(to.as_ref()); |
||||||
|
|
||||||
|
let fs_from = fsMakePath(PathType::UTF16.into(), from.as_ptr() as _); |
||||||
|
let fs_to = fsMakePath(PathType::UTF16.into(), to.as_ptr() as _); |
||||||
|
|
||||||
|
let r = FSUSER_RenameFile(arch.handle, fs_from, arch.handle, fs_to); |
||||||
|
if r == 0 { |
||||||
|
return Ok(()) |
||||||
|
} |
||||||
|
let r = FSUSER_RenameDirectory(arch.handle, fs_from, arch.handle, fs_to); |
||||||
|
if r == 0 { |
||||||
|
return Ok(()) |
||||||
|
} |
||||||
|
Err((r)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: Determine if we should check UTF-16 paths for interior NULs
|
||||||
|
fn to_utf16(path: &Path) -> Vec<u16> { |
||||||
|
path.as_os_str().encode_wide().collect::<Vec<_>>() |
||||||
|
} |
||||||
|
|
||||||
|
// Adapted from sys/windows/fs.rs in libstd
|
||||||
|
fn truncate_utf16_at_nul<'a>(v: &'a [u16]) -> &'a [u16] { |
||||||
|
match v.iter().position(|c| *c == 0) { |
||||||
|
// don't include the 0
|
||||||
|
Some(i) => &v[..i], |
||||||
|
None => v |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Adapted from sys/common/io.rs in libstd
|
||||||
|
unsafe fn read_to_end_uninitialized(f: &mut File, buf: &mut Vec<u8>) -> Result<usize, i32> { |
||||||
|
let start_len = buf.len(); |
||||||
|
buf.reserve(16); |
||||||
|
|
||||||
|
// Always try to read into the empty space of the vector (from the length to the capacity).
|
||||||
|
// If the vector ever fills up then we reserve an extra byte which should trigger the normal
|
||||||
|
// reallocation routines for the vector, which will likely double the size.
|
||||||
|
//
|
||||||
|
// This function is similar to the read_to_end function in std::io, but the logic about
|
||||||
|
// reservations and slicing is different enough that this is duplicated here.
|
||||||
|
loop { |
||||||
|
if buf.len() == buf.capacity() { |
||||||
|
buf.reserve(1); |
||||||
|
} |
||||||
|
|
||||||
|
let buf_slice = slice::from_raw_parts_mut(buf.as_mut_ptr().offset(buf.len() as isize), |
||||||
|
buf.capacity() - buf.len()); |
||||||
|
|
||||||
|
match f.read(buf_slice) { |
||||||
|
Ok(0) => { return Ok(buf.len() - start_len); } |
||||||
|
Ok(n) => { let len = buf.len() + n; buf.set_len(len); }, |
||||||
|
Err(e) => { return Err(e); } |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Drop for Fs { |
||||||
|
fn drop(&mut self) { |
||||||
|
unsafe { |
||||||
|
fsExit(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Drop for Archive { |
||||||
|
fn drop(&mut self) { |
||||||
|
unsafe { |
||||||
|
FSUSER_CloseArchive(self.handle); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Drop for File { |
||||||
|
fn drop(&mut self) { |
||||||
|
unsafe { |
||||||
|
FSFILE_Close(self.handle); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Drop for Dir { |
||||||
|
fn drop(&mut self) { |
||||||
|
unsafe { |
||||||
|
FSDIR_Close(self.0); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<PathType> for FS_PathType { |
||||||
|
fn from(p: PathType) -> Self { |
||||||
|
use self::PathType::*; |
||||||
|
use libctru::services::fs::FS_PathType::*; |
||||||
|
match p { |
||||||
|
Invalid => PATH_INVALID, |
||||||
|
Empty => PATH_EMPTY, |
||||||
|
Binary => PATH_BINARY, |
||||||
|
ASCII => PATH_ASCII, |
||||||
|
UTF16 => PATH_UTF16, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<ArchiveID> for FS_ArchiveID { |
||||||
|
fn from(a: ArchiveID) -> Self { |
||||||
|
use self::ArchiveID::*; |
||||||
|
use libctru::services::fs::FS_ArchiveID::*; |
||||||
|
match a { |
||||||
|
RomFS => ARCHIVE_ROMFS, |
||||||
|
Savedata => ARCHIVE_SAVEDATA, |
||||||
|
Extdata => ARCHIVE_EXTDATA, |
||||||
|
SharedExtdata => ARCHIVE_SHARED_EXTDATA, |
||||||
|
SystemSavedata => ARCHIVE_SYSTEM_SAVEDATA, |
||||||
|
Sdmc => ARCHIVE_SDMC, |
||||||
|
SdmcWriteOnly => ARCHIVE_SDMC_WRITE_ONLY, |
||||||
|
BossExtdata => ARCHIVE_BOSS_EXTDATA, |
||||||
|
CardSpiFS => ARCHIVE_CARD_SPIFS, |
||||||
|
ExtDataAndBossExtdata => ARCHIVE_EXTDATA_AND_BOSS_EXTDATA,
|
||||||
|
SystemSaveData2 => ARCHIVE_SYSTEM_SAVEDATA2, |
||||||
|
NandRW => ARCHIVE_NAND_RW, |
||||||
|
NandRO => ARCHIVE_NAND_RO, |
||||||
|
NandROWriteAccess => ARCHIVE_NAND_RO_WRITE_ACCESS, |
||||||
|
SaveDataAndContent => ARCHIVE_SAVEDATA_AND_CONTENT, |
||||||
|
SaveDataAndContent2 => ARCHIVE_SAVEDATA_AND_CONTENT2, |
||||||
|
NandCtrFS => ARCHIVE_NAND_CTR_FS, |
||||||
|
TwlPhoto => ARCHIVE_TWL_PHOTO, |
||||||
|
NandTwlFS => ARCHIVE_NAND_TWL_FS, |
||||||
|
GameCardSavedata => ARCHIVE_GAMECARD_SAVEDATA, |
||||||
|
UserSavedata => ARCHIVE_USER_SAVEDATA, |
||||||
|
DemoSavedata => ARCHIVE_DEMO_SAVEDATA, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
/// A trait for viewing representations from std types
|
||||||
|
#[doc(hidden)] |
||||||
|
pub trait AsInner<Inner: ?Sized> { |
||||||
|
fn as_inner(&self) -> &Inner; |
||||||
|
} |
||||||
|
|
||||||
|
/// A trait for viewing representations from std types
|
||||||
|
#[doc(hidden)] |
||||||
|
pub trait AsInnerMut<Inner: ?Sized> { |
||||||
|
fn as_inner_mut(&mut self) -> &mut Inner; |
||||||
|
} |
||||||
|
|
||||||
|
/// A trait for extracting representations from std types
|
||||||
|
#[doc(hidden)] |
||||||
|
pub trait IntoInner<Inner> { |
||||||
|
fn into_inner(self) -> Inner; |
||||||
|
} |
||||||
|
|
||||||
|
/// A trait for creating std types from internal representations
|
||||||
|
#[doc(hidden)] |
||||||
|
pub trait FromInner<Inner> { |
||||||
|
fn from_inner(inner: Inner) -> Self; |
||||||
|
} |
||||||
|
|
||||||
|
pub mod wtf8; |
Loading…
Reference in new issue