From 0c8a465e930f3aabfda39540e9eae22425c1cfe2 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 12 Aug 2016 15:59:22 -0700 Subject: [PATCH 01/28] Add Path and OsString APIs --- src/ascii.rs | 554 ++++++++ src/ffi/mod.rs | 3 + src/ffi/os_str.rs | 576 ++++++++ src/lib.rs | 23 +- src/path.rs | 3378 +++++++++++++++++++++++++++++++++++++++++++++ src/sys/mod.rs | 25 + src/sys/wtf8.rs | 1199 ++++++++++++++++ 7 files changed, 5754 insertions(+), 4 deletions(-) create mode 100644 src/ascii.rs create mode 100644 src/ffi/mod.rs create mode 100644 src/ffi/os_str.rs create mode 100644 src/path.rs create mode 100644 src/sys/mod.rs create mode 100644 src/sys/wtf8.rs diff --git a/src/ascii.rs b/src/ascii.rs new file mode 100644 index 0000000..93f447b --- /dev/null +++ b/src/ascii.rs @@ -0,0 +1,554 @@ +// Copyright 2013-2014 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Operations on ASCII strings and characters. + +use core::mem; +use core::ops::Range; +use collections::{String, Vec}; + +/// Extension methods for ASCII-subset only operations on string slices. +/// +/// Be aware that operations on seemingly non-ASCII characters can sometimes +/// have unexpected results. Consider this example: +/// +/// ``` +/// use std::ascii::AsciiExt; +/// +/// assert_eq!("café".to_ascii_uppercase(), "CAFÉ"); +/// assert_eq!("café".to_ascii_uppercase(), "CAFé"); +/// ``` +/// +/// In the first example, the lowercased string is represented `"cafe\u{301}"` +/// (the last character is an acute accent [combining character]). Unlike the +/// other characters in the string, the combining character will not get mapped +/// to an uppercase variant, resulting in `"CAFE\u{301}"`. In the second +/// example, the lowercased string is represented `"caf\u{e9}"` (the last +/// character is a single Unicode character representing an 'e' with an acute +/// accent). Since the last character is defined outside the scope of ASCII, +/// it will not get mapped to an uppercase variant, resulting in `"CAF\u{e9}"`. +/// +/// [combining character]: https://en.wikipedia.org/wiki/Combining_character +pub trait AsciiExt { + /// Container type for copied ASCII characters. + type Owned; + + /// Checks if the value is within the ASCII range. + /// + /// # Examples + /// + /// ``` + /// use std::ascii::AsciiExt; + /// + /// let ascii = 'a'; + /// let utf8 = '❤'; + /// + /// assert!(ascii.is_ascii()); + /// assert!(!utf8.is_ascii()); + /// ``` + fn is_ascii(&self) -> bool; + + /// Makes a copy of the string in ASCII upper case. + /// + /// ASCII letters 'a' to 'z' are mapped to 'A' to 'Z', + /// but non-ASCII letters are unchanged. + /// + /// # Examples + /// + /// ``` + /// use std::ascii::AsciiExt; + /// + /// let ascii = 'a'; + /// let utf8 = '❤'; + /// + /// assert_eq!('A', ascii.to_ascii_uppercase()); + /// assert_eq!('❤', utf8.to_ascii_uppercase()); + /// ``` + fn to_ascii_uppercase(&self) -> Self::Owned; + + /// Makes a copy of the string in ASCII lower case. + /// + /// ASCII letters 'A' to 'Z' are mapped to 'a' to 'z', + /// but non-ASCII letters are unchanged. + /// + /// # Examples + /// + /// ``` + /// use std::ascii::AsciiExt; + /// + /// let ascii = 'A'; + /// let utf8 = '❤'; + /// + /// assert_eq!('a', ascii.to_ascii_lowercase()); + /// assert_eq!('❤', utf8.to_ascii_lowercase()); + /// ``` + fn to_ascii_lowercase(&self) -> Self::Owned; + + /// Checks that two strings are an ASCII case-insensitive match. + /// + /// Same as `to_ascii_lowercase(a) == to_ascii_lowercase(b)`, + /// but without allocating and copying temporary strings. + /// + /// # Examples + /// + /// ``` + /// use std::ascii::AsciiExt; + /// + /// let ascii1 = 'A'; + /// let ascii2 = 'a'; + /// let ascii3 = 'A'; + /// let ascii4 = 'z'; + /// + /// assert!(ascii1.eq_ignore_ascii_case(&ascii2)); + /// assert!(ascii1.eq_ignore_ascii_case(&ascii3)); + /// assert!(!ascii1.eq_ignore_ascii_case(&ascii4)); + /// ``` + fn eq_ignore_ascii_case(&self, other: &Self) -> bool; + + /// Converts this type to its ASCII upper case equivalent in-place. + /// + /// See `to_ascii_uppercase` for more information. + /// + /// # Examples + /// + /// ``` + /// use std::ascii::AsciiExt; + /// + /// let mut ascii = 'a'; + /// + /// ascii.make_ascii_uppercase(); + /// + /// assert_eq!('A', ascii); + /// ``` + fn make_ascii_uppercase(&mut self); + + /// Converts this type to its ASCII lower case equivalent in-place. + /// + /// See `to_ascii_lowercase` for more information. + /// + /// # Examples + /// + /// ``` + /// use std::ascii::AsciiExt; + /// + /// let mut ascii = 'A'; + /// + /// ascii.make_ascii_lowercase(); + /// + /// assert_eq!('a', ascii); + /// ``` + fn make_ascii_lowercase(&mut self); +} + +impl AsciiExt for str { + type Owned = String; + + #[inline] + fn is_ascii(&self) -> bool { + self.bytes().all(|b| b.is_ascii()) + } + + #[inline] + fn to_ascii_uppercase(&self) -> String { + let mut bytes = self.as_bytes().to_vec(); + bytes.make_ascii_uppercase(); + // make_ascii_uppercase() preserves the UTF-8 invariant. + unsafe { String::from_utf8_unchecked(bytes) } + } + + #[inline] + fn to_ascii_lowercase(&self) -> String { + let mut bytes = self.as_bytes().to_vec(); + bytes.make_ascii_lowercase(); + // make_ascii_uppercase() preserves the UTF-8 invariant. + unsafe { String::from_utf8_unchecked(bytes) } + } + + #[inline] + fn eq_ignore_ascii_case(&self, other: &str) -> bool { + self.as_bytes().eq_ignore_ascii_case(other.as_bytes()) + } + + fn make_ascii_uppercase(&mut self) { + let me: &mut [u8] = unsafe { mem::transmute(self) }; + me.make_ascii_uppercase() + } + + fn make_ascii_lowercase(&mut self) { + let me: &mut [u8] = unsafe { mem::transmute(self) }; + me.make_ascii_lowercase() + } +} + +impl AsciiExt for [u8] { + type Owned = Vec; + #[inline] + fn is_ascii(&self) -> bool { + self.iter().all(|b| b.is_ascii()) + } + + #[inline] + fn to_ascii_uppercase(&self) -> Vec { + let mut me = self.to_vec(); + me.make_ascii_uppercase(); + return me + } + + #[inline] + fn to_ascii_lowercase(&self) -> Vec { + let mut me = self.to_vec(); + me.make_ascii_lowercase(); + return me + } + + #[inline] + fn eq_ignore_ascii_case(&self, other: &[u8]) -> bool { + self.len() == other.len() && + self.iter().zip(other).all(|(a, b)| { + a.eq_ignore_ascii_case(b) + }) + } + + fn make_ascii_uppercase(&mut self) { + for byte in self { + byte.make_ascii_uppercase(); + } + } + + fn make_ascii_lowercase(&mut self) { + for byte in self { + byte.make_ascii_lowercase(); + } + } +} + +impl AsciiExt for u8 { + type Owned = u8; + #[inline] + fn is_ascii(&self) -> bool { *self & 128 == 0 } + #[inline] + fn to_ascii_uppercase(&self) -> u8 { ASCII_UPPERCASE_MAP[*self as usize] } + #[inline] + fn to_ascii_lowercase(&self) -> u8 { ASCII_LOWERCASE_MAP[*self as usize] } + #[inline] + fn eq_ignore_ascii_case(&self, other: &u8) -> bool { + self.to_ascii_lowercase() == other.to_ascii_lowercase() + } + #[inline] + fn make_ascii_uppercase(&mut self) { *self = self.to_ascii_uppercase(); } + #[inline] + fn make_ascii_lowercase(&mut self) { *self = self.to_ascii_lowercase(); } +} + +impl AsciiExt for char { + type Owned = char; + #[inline] + fn is_ascii(&self) -> bool { + *self as u32 <= 0x7F + } + + #[inline] + fn to_ascii_uppercase(&self) -> char { + if self.is_ascii() { + (*self as u8).to_ascii_uppercase() as char + } else { + *self + } + } + + #[inline] + fn to_ascii_lowercase(&self) -> char { + if self.is_ascii() { + (*self as u8).to_ascii_lowercase() as char + } else { + *self + } + } + + #[inline] + fn eq_ignore_ascii_case(&self, other: &char) -> bool { + self.to_ascii_lowercase() == other.to_ascii_lowercase() + } + + #[inline] + fn make_ascii_uppercase(&mut self) { *self = self.to_ascii_uppercase(); } + #[inline] + fn make_ascii_lowercase(&mut self) { *self = self.to_ascii_lowercase(); } +} + +/// An iterator over the escaped version of a byte, constructed via +/// `std::ascii::escape_default`. +pub struct EscapeDefault { + range: Range, + data: [u8; 4], +} + +/// Returns an iterator that produces an escaped version of a `u8`. +/// +/// The default is chosen with a bias toward producing literals that are +/// legal in a variety of languages, including C++11 and similar C-family +/// languages. The exact rules are: +/// +/// - Tab, CR and LF are escaped as '\t', '\r' and '\n' respectively. +/// - Single-quote, double-quote and backslash chars are backslash-escaped. +/// - Any other chars in the range [0x20,0x7e] are not escaped. +/// - Any other chars are given hex escapes of the form '\xNN'. +/// - Unicode escapes are never generated by this function. +/// +/// # Examples +/// +/// ``` +/// use std::ascii; +/// +/// let escaped = ascii::escape_default(b'0').next().unwrap(); +/// assert_eq!(b'0', escaped); +/// +/// let mut escaped = ascii::escape_default(b'\t'); +/// +/// assert_eq!(b'\\', escaped.next().unwrap()); +/// assert_eq!(b't', escaped.next().unwrap()); +/// ``` +pub fn escape_default(c: u8) -> EscapeDefault { + let (data, len) = match c { + b'\t' => ([b'\\', b't', 0, 0], 2), + b'\r' => ([b'\\', b'r', 0, 0], 2), + b'\n' => ([b'\\', b'n', 0, 0], 2), + b'\\' => ([b'\\', b'\\', 0, 0], 2), + b'\'' => ([b'\\', b'\'', 0, 0], 2), + b'"' => ([b'\\', b'"', 0, 0], 2), + b'\x20' ... b'\x7e' => ([c, 0, 0, 0], 1), + _ => ([b'\\', b'x', hexify(c >> 4), hexify(c & 0xf)], 4), + }; + + return EscapeDefault { range: (0.. len), data: data }; + + fn hexify(b: u8) -> u8 { + match b { + 0 ... 9 => b'0' + b, + _ => b'a' + b - 10, + } + } +} + +impl Iterator for EscapeDefault { + type Item = u8; + fn next(&mut self) -> Option { self.range.next().map(|i| self.data[i]) } + fn size_hint(&self) -> (usize, Option) { self.range.size_hint() } +} +impl DoubleEndedIterator for EscapeDefault { + fn next_back(&mut self) -> Option { + self.range.next_back().map(|i| self.data[i]) + } +} +impl ExactSizeIterator for EscapeDefault {} + +static ASCII_LOWERCASE_MAP: [u8; 256] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + b' ', b'!', b'"', b'#', b'$', b'%', b'&', b'\'', + b'(', b')', b'*', b'+', b',', b'-', b'.', b'/', + b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', + b'8', b'9', b':', b';', b'<', b'=', b'>', b'?', + b'@', + + b'a', b'b', b'c', b'd', b'e', b'f', b'g', + b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', + b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', + b'x', b'y', b'z', + + b'[', b'\\', b']', b'^', b'_', + b'`', b'a', b'b', b'c', b'd', b'e', b'f', b'g', + b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', + b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', + b'x', b'y', b'z', b'{', b'|', b'}', b'~', 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, +]; + +static ASCII_UPPERCASE_MAP: [u8; 256] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + b' ', b'!', b'"', b'#', b'$', b'%', b'&', b'\'', + b'(', b')', b'*', b'+', b',', b'-', b'.', b'/', + b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', + b'8', b'9', b':', b';', b'<', b'=', b'>', b'?', + b'@', b'A', b'B', b'C', b'D', b'E', b'F', b'G', + b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', + b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', + b'X', b'Y', b'Z', b'[', b'\\', b']', b'^', b'_', + b'`', + + b'A', b'B', b'C', b'D', b'E', b'F', b'G', + b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', + b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', + b'X', b'Y', b'Z', + + b'{', b'|', b'}', b'~', 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, +]; + + +#[cfg(test)] +mod tests { + use super::*; + use rustc_unicode::char::from_u32; + use collections::string::ToString; + + #[test] + fn test_is_ascii() { + assert!(b"".is_ascii()); + assert!(b"banana\0\x7F".is_ascii()); + assert!(b"banana\0\x7F".iter().all(|b| b.is_ascii())); + assert!(!b"Vi\xe1\xbb\x87t Nam".is_ascii()); + assert!(!b"Vi\xe1\xbb\x87t Nam".iter().all(|b| b.is_ascii())); + assert!(!b"\xe1\xbb\x87".iter().any(|b| b.is_ascii())); + + assert!("".is_ascii()); + assert!("banana\0\u{7F}".is_ascii()); + assert!("banana\0\u{7F}".chars().all(|c| c.is_ascii())); + assert!(!"ประเทศไทย中华Việt Nam".chars().all(|c| c.is_ascii())); + + // NOTE: This test fails for some reason. + assert!(!"ประเทศไทย中华ệ ".chars().any(|c| c.is_ascii())); + } + + #[test] + fn test_to_ascii_uppercase() { + assert_eq!("url()URL()uRl()ürl".to_ascii_uppercase(), "URL()URL()URL()üRL"); + assert_eq!("hıKß".to_ascii_uppercase(), "HıKß"); + + for i in 0..501 { + let upper = if 'a' as u32 <= i && i <= 'z' as u32 { i + 'A' as u32 - 'a' as u32 } + else { i }; + assert_eq!((from_u32(i).unwrap()).to_string().to_ascii_uppercase(), + (from_u32(upper).unwrap()).to_string()); + } + } + + #[test] + fn test_to_ascii_lowercase() { + assert_eq!("url()URL()uRl()Ürl".to_ascii_lowercase(), "url()url()url()Ürl"); + // Dotted capital I, Kelvin sign, Sharp S. + assert_eq!("HİKß".to_ascii_lowercase(), "hİKß"); + + for i in 0..501 { + let lower = if 'A' as u32 <= i && i <= 'Z' as u32 { i + 'a' as u32 - 'A' as u32 } + else { i }; + assert_eq!((from_u32(i).unwrap()).to_string().to_ascii_lowercase(), + (from_u32(lower).unwrap()).to_string()); + } + } + + #[test] + fn test_make_ascii_lower_case() { + macro_rules! test { + ($from: expr, $to: expr) => { + { + let mut x = $from; + x.make_ascii_lowercase(); + assert_eq!(x, $to); + } + } + } + test!(b'A', b'a'); + test!(b'a', b'a'); + test!(b'!', b'!'); + test!('A', 'a'); + test!('À', 'À'); + test!('a', 'a'); + test!('!', '!'); + test!(b"H\xc3\x89".to_vec(), b"h\xc3\x89"); + test!("HİKß".to_string(), "hİKß"); + } + + + #[test] + fn test_make_ascii_upper_case() { + macro_rules! test { + ($from: expr, $to: expr) => { + { + let mut x = $from; + x.make_ascii_uppercase(); + assert_eq!(x, $to); + } + } + } + test!(b'a', b'A'); + test!(b'A', b'A'); + test!(b'!', b'!'); + test!('a', 'A'); + test!('à', 'à'); + test!('A', 'A'); + test!('!', '!'); + test!(b"h\xc3\xa9".to_vec(), b"H\xc3\xa9"); + test!("hıKß".to_string(), "HıKß"); + + let mut x = "Hello".to_string(); + x[..3].make_ascii_uppercase(); // Test IndexMut on String. + assert_eq!(x, "HELlo") + } + + #[test] + fn test_eq_ignore_ascii_case() { + assert!("url()URL()uRl()Ürl".eq_ignore_ascii_case("url()url()url()Ürl")); + assert!(!"Ürl".eq_ignore_ascii_case("ürl")); + // Dotted capital I, Kelvin sign, Sharp S. + assert!("HİKß".eq_ignore_ascii_case("hİKß")); + assert!(!"İ".eq_ignore_ascii_case("i")); + assert!(!"K".eq_ignore_ascii_case("k")); + assert!(!"ß".eq_ignore_ascii_case("s")); + + for i in 0..501 { + let lower = if 'A' as u32 <= i && i <= 'Z' as u32 { i + 'a' as u32 - 'A' as u32 } + else { i }; + assert!((from_u32(i).unwrap()).to_string().eq_ignore_ascii_case( + &from_u32(lower).unwrap().to_string())); + } + } + + #[test] + fn inference_works() { + let x = "a".to_string(); + x.eq_ignore_ascii_case("A"); + } +} diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs new file mode 100644 index 0000000..3a2d5f1 --- /dev/null +++ b/src/ffi/mod.rs @@ -0,0 +1,3 @@ +pub use self::os_str::{OsString, OsStr}; + +mod os_str; diff --git a/src/ffi/os_str.rs b/src/ffi/os_str.rs new file mode 100644 index 0000000..d062d55 --- /dev/null +++ b/src/ffi/os_str.rs @@ -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 or the MIT license +// , 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) -> Option { + 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 { + self.inner.into_string().map_err(|buf| OsString { inner: buf} ) + } + + /// Extends the string with the given `&OsStr` slice. + pub fn push>(&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 for OsString { + fn from(s: String) -> OsString { + OsString { inner: Wtf8Buf::from_string(s) } + } +} + +impl<'a, T: ?Sized + AsRef> From<&'a T> for OsString { + fn from(s: &'a T) -> OsString { + s.as_ref().to_os_string() + } +} + +impl ops::Index 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 for OsString { + fn eq(&self, other: &str) -> bool { + &**self == other + } +} + +impl PartialEq 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 { + (&**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 for OsString { + #[inline] + fn partial_cmp(&self, other: &str) -> Option { + (&**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(&self, state: &mut H) { + (&**self).hash(state) + } +} + +impl OsStr { + /// Coerces into an `OsStr` slice. + pub fn new + ?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`. + /// + /// Any non-Unicode sequences are replaced with U+FFFD REPLACEMENT CHARACTER. + pub fn to_string_lossy(&self) -> Cow { + 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 for OsStr { + fn eq(&self, other: &str) -> bool { + *self == *OsStr::new(other) + } +} + +impl PartialEq 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 { + 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 for OsStr { + #[inline] + fn partial_cmp(&self, other: &str) -> Option { + self.partial_cmp(OsStr::new(other)) + } +} + +// FIXME (#19470): cannot provide PartialOrd 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 { ::eq(self, other) } + } + + impl<'a, 'b> PartialEq<$lhs> for $rhs { + #[inline] + fn eq(&self, other: &$lhs) -> bool { ::eq(self, other) } + } + + impl<'a, 'b> PartialOrd<$rhs> for $lhs { + #[inline] + fn partial_cmp(&self, other: &$rhs) -> Option { + ::partial_cmp(self, other) + } + } + + impl<'a, 'b> PartialOrd<$lhs> for $rhs { + #[inline] + fn partial_cmp(&self, other: &$lhs) -> Option { + ::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(&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 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 for OsStr { + fn as_ref(&self) -> &OsStr { + self + } +} + +impl AsRef for OsString { + fn as_ref(&self) -> &OsStr { + self + } +} + +impl AsRef for str { + fn as_ref(&self) -> &OsStr { + OsStr::from_inner(Wtf8::from_str(self)) + } +} + +impl AsRef for String { + fn as_ref(&self) -> &OsStr { + (&**self).as_ref() + } +} + +impl FromInner for OsString { + fn from_inner(buf: Wtf8Buf) -> OsString { + OsString { inner: buf } + } +} + +impl IntoInner for OsString { + fn into_inner(self) -> Wtf8Buf { + self.inner + } +} + +impl AsInner 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); + } +} diff --git a/src/lib.rs b/src/lib.rs index dc225c6..7fa8597 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,20 +1,35 @@ -#![feature(alloc, collections, lang_items)] +#![feature(alloc)] +#![feature(collections)] +#![feature(char_escape_debug)] +#![feature(lang_items)] +#![feature(question_mark)] +#![feature(slice_patterns)] +#![feature(str_internals)] +#![feature(unicode)] + #![no_std] + #![crate_type = "rlib"] #![crate_name = "ctru"] -extern crate ctru_sys as libctru; - extern crate alloc; extern crate collections; +extern crate rustc_unicode; + +extern crate ctru_sys as libctru; pub mod console; pub mod srv; pub mod gfx; +pub mod services; pub mod sdmc; -pub mod services; +pub mod ascii; +pub mod ffi; pub mod panic; +pub mod path; + +mod sys; pub use srv::Srv; pub use gfx::Gfx; diff --git a/src/path.rs b/src/path.rs new file mode 100644 index 0000000..3fb6357 --- /dev/null +++ b/src/path.rs @@ -0,0 +1,3378 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Cross-platform path manipulation. +//! +//! This module provides two types, `PathBuf` and `Path` (akin to `String` and +//! `str`), for working with paths abstractly. These types are thin wrappers +//! around `OsString` and `OsStr` respectively, meaning that they work directly +//! on strings according to the local platform's path syntax. +//! +//! ## Simple usage +//! +//! Path manipulation includes both parsing components from slices and building +//! new owned paths. +//! +//! To parse a path, you can create a `Path` slice from a `str` +//! slice and start asking questions: +//! +//! ```rust +//! use std::path::Path; +//! +//! let path = Path::new("/tmp/foo/bar.txt"); +//! let file = path.file_name(); +//! let extension = path.extension(); +//! let parent_dir = path.parent(); +//! ``` +//! +//! To build or modify paths, use `PathBuf`: +//! +//! ```rust +//! use std::path::PathBuf; +//! +//! let mut path = PathBuf::from("c:\\"); +//! path.push("windows"); +//! path.push("system32"); +//! path.set_extension("dll"); +//! ``` +//! +//! ## Path components and normalization +//! +//! The path APIs are built around the notion of "components", which roughly +//! correspond to the substrings between path separators (`/` and, on Windows, +//! `\`). The APIs for path parsing are largely specified in terms of the path's +//! components, so it's important to clearly understand how those are +//! determined. +//! +//! A path can always be reconstructed into an *equivalent* path by +//! putting together its components via `push`. Syntactically, the +//! paths may differ by the normalization described below. +//! +//! ### Component types +//! +//! Components come in several types: +//! +//! * Normal components are the default: standard references to files or +//! directories. The path `a/b` has two normal components, `a` and `b`. +//! +//! * Current directory components represent the `.` character. For example, +//! `./a` has a current directory component and a normal component `a`. +//! +//! * The root directory component represents a separator that designates +//! starting from root. For example, `/a/b` has a root directory component +//! followed by normal components `a` and `b`. +//! +//! On Windows, an additional component type comes into play: +//! +//! * Prefix components, of which there is a large variety. For example, `C:` +//! and `\\server\share` are prefixes. The path `C:windows` has a prefix +//! component `C:` and a normal component `windows`; the path `C:\windows` has a +//! prefix component `C:`, a root directory component, and a normal component +//! `windows`. +//! +//! ### Normalization +//! +//! Aside from splitting on the separator(s), there is a small amount of +//! "normalization": +//! +//! * Repeated separators are ignored: `a/b` and `a//b` both have components `a` +//! and `b`. +//! +//! * Occurrences of `.` are normalized away, *except* if they are at +//! the beginning of the path (in which case they are often meaningful +//! in terms of path searching). So, for example, `a/./b`, `a/b/`, +//! `/a/b/.` and `a/b` all have components `a` and `b`, but `./a/b` +//! has a leading current directory component. +//! +//! No other normalization takes place by default. In particular, +//! `a/c` and `a/b/../c` are distinct, to account for the possibility +//! that `b` is a symbolic link (so its parent isn't `a`). Further +//! normalization is possible to build on top of the components APIs, +//! and will be included in this library in the near future. + +use ascii::*; +use collections::borrow::{Borrow, ToOwned, Cow}; +use core::cmp; +//use error::Error; +use core::fmt; +//use fs; +use core::hash::{Hash, Hasher}; +//use io; +use core::iter; +use core::mem; +use core::ops::{self, Deref}; +use collections::String; +use collections::Vec; + +use ffi::{OsStr, OsString}; + +use self::platform::{is_sep_byte, is_verbatim_sep, MAIN_SEP_STR, parse_prefix}; + +//////////////////////////////////////////////////////////////////////////////// +// GENERAL NOTES +//////////////////////////////////////////////////////////////////////////////// +// +// Parsing in this module is done by directly transmuting OsStr to [u8] slices, +// taking advantage of the fact that OsStr always encodes ASCII characters +// as-is. Eventually, this transmutation should be replaced by direct uses of +// OsStr APIs for parsing, but it will take a while for those to become +// available. + +//////////////////////////////////////////////////////////////////////////////// +// Platform-specific definitions +//////////////////////////////////////////////////////////////////////////////// + +// The following modules give the most basic tools for parsing paths on various +// platforms. The bulk of the code is devoted to parsing prefixes on Windows. + +#[cfg(unix)] +mod platform { + use super::Prefix; + use ffi::OsStr; + + #[inline] + pub fn is_sep_byte(b: u8) -> bool { + b == b'/' + } + + #[inline] + pub fn is_verbatim_sep(b: u8) -> bool { + b == b'/' + } + + pub fn parse_prefix(_: &OsStr) -> Option { + None + } + + pub const MAIN_SEP_STR: &'static str = "/"; + pub const MAIN_SEP: char = '/'; +} + +#[cfg(windows)] +mod platform { + use ascii::*; + + use super::{os_str_as_u8_slice, u8_slice_as_os_str, Prefix}; + use ffi::OsStr; + + #[inline] + pub fn is_sep_byte(b: u8) -> bool { + b == b'/' || b == b'\\' + } + + #[inline] + pub fn is_verbatim_sep(b: u8) -> bool { + b == b'\\' + } + + pub fn parse_prefix<'a>(path: &'a OsStr) -> Option { + use super::Prefix::*; + unsafe { + // The unsafety here stems from converting between &OsStr and &[u8] + // and back. This is safe to do because (1) we only look at ASCII + // contents of the encoding and (2) new &OsStr values are produced + // only from ASCII-bounded slices of existing &OsStr values. + let mut path = os_str_as_u8_slice(path); + + if path.starts_with(br"\\") { + // \\ + path = &path[2..]; + if path.starts_with(br"?\") { + // \\?\ + path = &path[2..]; + if path.starts_with(br"UNC\") { + // \\?\UNC\server\share + path = &path[4..]; + let (server, share) = match parse_two_comps(path, is_verbatim_sep) { + Some((server, share)) => + (u8_slice_as_os_str(server), u8_slice_as_os_str(share)), + None => (u8_slice_as_os_str(path), u8_slice_as_os_str(&[])), + }; + return Some(VerbatimUNC(server, share)); + } else { + // \\?\path + let idx = path.iter().position(|&b| b == b'\\'); + if idx == Some(2) && path[1] == b':' { + let c = path[0]; + if c.is_ascii() && (c as char).is_alphabetic() { + // \\?\C:\ path + return Some(VerbatimDisk(c.to_ascii_uppercase())); + } + } + let slice = &path[..idx.unwrap_or(path.len())]; + return Some(Verbatim(u8_slice_as_os_str(slice))); + } + } else if path.starts_with(b".\\") { + // \\.\path + path = &path[2..]; + let pos = path.iter().position(|&b| b == b'\\'); + let slice = &path[..pos.unwrap_or(path.len())]; + return Some(DeviceNS(u8_slice_as_os_str(slice))); + } + match parse_two_comps(path, is_sep_byte) { + Some((server, share)) if !server.is_empty() && !share.is_empty() => { + // \\server\share + return Some(UNC(u8_slice_as_os_str(server), u8_slice_as_os_str(share))); + } + _ => (), + } + } else if path.get(1) == Some(& b':') { + // C: + let c = path[0]; + if c.is_ascii() && (c as char).is_alphabetic() { + return Some(Disk(c.to_ascii_uppercase())); + } + } + return None; + } + + fn parse_two_comps(mut path: &[u8], f: fn(u8) -> bool) -> Option<(&[u8], &[u8])> { + let first = match path.iter().position(|x| f(*x)) { + None => return None, + Some(x) => &path[..x], + }; + path = &path[(first.len() + 1)..]; + let idx = path.iter().position(|x| f(*x)); + let second = &path[..idx.unwrap_or(path.len())]; + Some((first, second)) + } + } + + pub const MAIN_SEP_STR: &'static str = "\\"; + pub const MAIN_SEP: char = '\\'; +} + +//////////////////////////////////////////////////////////////////////////////// +// Windows Prefixes +//////////////////////////////////////////////////////////////////////////////// + +/// Path prefixes (Windows only). +/// +/// Windows uses a variety of path styles, including references to drive +/// volumes (like `C:`), network shared folders (like `\\server\share`) and +/// others. In addition, some path prefixes are "verbatim", in which case +/// `/` is *not* treated as a separator and essentially no normalization is +/// performed. +#[derive(Copy, Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] +pub enum Prefix<'a> { + /// Prefix `\\?\`, together with the given component immediately following it. + Verbatim(&'a OsStr), + /// Prefix `\\?\UNC\`, with the "server" and "share" components following it. + VerbatimUNC( + &'a OsStr, + &'a OsStr, + ), + + /// Prefix like `\\?\C:\`, for the given drive letter + VerbatimDisk(u8), + + /// Prefix `\\.\`, together with the given component immediately following it. + DeviceNS(&'a OsStr), + + /// Prefix `\\server\share`, with the given "server" and "share" components. + UNC( + &'a OsStr, + &'a OsStr, + ), + + /// Prefix `C:` for the given disk drive. + Disk(u8), +} + +impl<'a> Prefix<'a> { + #[inline] + fn len(&self) -> usize { + use self::Prefix::*; + fn os_str_len(s: &OsStr) -> usize { + os_str_as_u8_slice(s).len() + } + match *self { + Verbatim(x) => 4 + os_str_len(x), + VerbatimUNC(x, y) => { + 8 + os_str_len(x) + + if os_str_len(y) > 0 { + 1 + os_str_len(y) + } else { + 0 + } + }, + VerbatimDisk(_) => 6, + UNC(x, y) => { + 2 + os_str_len(x) + + if os_str_len(y) > 0 { + 1 + os_str_len(y) + } else { + 0 + } + }, + DeviceNS(x) => 4 + os_str_len(x), + Disk(_) => 2, + } + + } + + /// Determines if the prefix is verbatim, i.e. begins with `\\?\`. + #[inline] + pub fn is_verbatim(&self) -> bool { + use self::Prefix::*; + match *self { + Verbatim(_) | VerbatimDisk(_) | VerbatimUNC(_, _) => true, + _ => false, + } + } + + #[inline] + fn is_drive(&self) -> bool { + match *self { + Prefix::Disk(_) => true, + _ => false, + } + } + + #[inline] + fn has_implicit_root(&self) -> bool { + !self.is_drive() + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Exposed parsing helpers +//////////////////////////////////////////////////////////////////////////////// + +/// Determines whether the character is one of the permitted path +/// separators for the current platform. +/// +/// # Examples +/// +/// ``` +/// use std::path; +/// +/// assert!(path::is_separator('/')); +/// assert!(!path::is_separator('❤')); +/// ``` +pub fn is_separator(c: char) -> bool { + c.is_ascii() && is_sep_byte(c as u8) +} + +/// The primary separator for the current platform +pub const MAIN_SEPARATOR: char = platform::MAIN_SEP; + +//////////////////////////////////////////////////////////////////////////////// +// Misc helpers +//////////////////////////////////////////////////////////////////////////////// + +// Iterate through `iter` while it matches `prefix`; return `None` if `prefix` +// is not a prefix of `iter`, otherwise return `Some(iter_after_prefix)` giving +// `iter` after having exhausted `prefix`. +fn iter_after(mut iter: I, mut prefix: J) -> Option + where I: Iterator + Clone, + J: Iterator, + A: PartialEq +{ + loop { + let mut iter_next = iter.clone(); + match (iter_next.next(), prefix.next()) { + (Some(ref x), Some(ref y)) if x == y => (), + (Some(_), Some(_)) => return None, + (Some(_), None) => return Some(iter), + (None, None) => return Some(iter), + (None, Some(_)) => return None, + } + iter = iter_next; + } +} + +// See note at the top of this module to understand why these are used: +fn os_str_as_u8_slice(s: &OsStr) -> &[u8] { + unsafe { mem::transmute(s) } +} +unsafe fn u8_slice_as_os_str(s: &[u8]) -> &OsStr { + mem::transmute(s) +} + +//////////////////////////////////////////////////////////////////////////////// +// Cross-platform, iterator-independent parsing +//////////////////////////////////////////////////////////////////////////////// + +/// Says whether the first byte after the prefix is a separator. +fn has_physical_root(s: &[u8], prefix: Option) -> bool { + let path = if let Some(p) = prefix { + &s[p.len()..] + } else { + s + }; + !path.is_empty() && is_sep_byte(path[0]) +} + +// basic workhorse for splitting stem and extension +fn split_file_at_dot(file: &OsStr) -> (Option<&OsStr>, Option<&OsStr>) { + unsafe { + if os_str_as_u8_slice(file) == b".." { + return (Some(file), None); + } + + // The unsafety here stems from converting between &OsStr and &[u8] + // and back. This is safe to do because (1) we only look at ASCII + // contents of the encoding and (2) new &OsStr values are produced + // only from ASCII-bounded slices of existing &OsStr values. + + let mut iter = os_str_as_u8_slice(file).rsplitn(2, |b| *b == b'.'); + let after = iter.next(); + let before = iter.next(); + if before == Some(b"") { + (Some(file), None) + } else { + (before.map(|s| u8_slice_as_os_str(s)), + after.map(|s| u8_slice_as_os_str(s))) + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// The core iterators +//////////////////////////////////////////////////////////////////////////////// + +/// Component parsing works by a double-ended state machine; the cursors at the +/// front and back of the path each keep track of what parts of the path have +/// been consumed so far. +/// +/// Going front to back, a path is made up of a prefix, a starting +/// directory component, and a body (of normal components) +#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] +enum State { + Prefix = 0, // c: + StartDir = 1, // / or . or nothing + Body = 2, // foo/bar/baz + Done = 3, +} + +/// A Windows path prefix, e.g. `C:` or `\\server\share`. +/// +/// Does not occur on Unix. +#[derive(Copy, Clone, Eq, Debug)] +pub struct PrefixComponent<'a> { + /// The prefix as an unparsed `OsStr` slice. + raw: &'a OsStr, + + /// The parsed prefix data. + parsed: Prefix<'a>, +} + +impl<'a> PrefixComponent<'a> { + /// The parsed prefix data. + pub fn kind(&self) -> Prefix<'a> { + self.parsed + } + + /// The raw `OsStr` slice for this prefix. + pub fn as_os_str(&self) -> &'a OsStr { + self.raw + } +} + +impl<'a> cmp::PartialEq for PrefixComponent<'a> { + fn eq(&self, other: &PrefixComponent<'a>) -> bool { + cmp::PartialEq::eq(&self.parsed, &other.parsed) + } +} + +impl<'a> cmp::PartialOrd for PrefixComponent<'a> { + fn partial_cmp(&self, other: &PrefixComponent<'a>) -> Option { + cmp::PartialOrd::partial_cmp(&self.parsed, &other.parsed) + } +} + +impl<'a> cmp::Ord for PrefixComponent<'a> { + fn cmp(&self, other: &PrefixComponent<'a>) -> cmp::Ordering { + cmp::Ord::cmp(&self.parsed, &other.parsed) + } +} + +impl<'a> Hash for PrefixComponent<'a> { + fn hash(&self, h: &mut H) { + self.parsed.hash(h); + } +} + +/// A single component of a path. +/// +/// See the module documentation for an in-depth explanation of components and +/// their role in the API. +/// +/// This `enum` is created from iterating over the [`path::Components`] +/// `struct`. +/// +/// # Examples +/// +/// ```rust +/// use std::path::{Component, Path}; +/// +/// let path = Path::new("/tmp/foo/bar.txt"); +/// let components = path.components().collect::>(); +/// assert_eq!(&components, &[ +/// Component::RootDir, +/// Component::Normal("tmp".as_ref()), +/// Component::Normal("foo".as_ref()), +/// Component::Normal("bar.txt".as_ref()), +/// ]); +/// ``` +/// +/// [`path::Components`]: struct.Components.html +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum Component<'a> { + /// A Windows path prefix, e.g. `C:` or `\\server\share`. + /// + /// Does not occur on Unix. + Prefix( + PrefixComponent<'a> + ), + + /// The root directory component, appears after any prefix and before anything else + RootDir, + + /// A reference to the current directory, i.e. `.` + CurDir, + + /// A reference to the parent directory, i.e. `..` + ParentDir, + + /// A normal component, i.e. `a` and `b` in `a/b` + Normal(&'a OsStr), +} + +impl<'a> Component<'a> { + /// Extracts the underlying `OsStr` slice + pub fn as_os_str(self) -> &'a OsStr { + match self { + Component::Prefix(p) => p.as_os_str(), + Component::RootDir => OsStr::new(MAIN_SEP_STR), + Component::CurDir => OsStr::new("."), + Component::ParentDir => OsStr::new(".."), + Component::Normal(path) => path, + } + } +} + +impl<'a> AsRef for Component<'a> { + fn as_ref(&self) -> &OsStr { + self.as_os_str() + } +} + +/// The core iterator giving the components of a path. +/// +/// See the module documentation for an in-depth explanation of components and +/// their role in the API. +/// +/// This `struct` is created by the [`path::Path::components`] method. +/// +/// # Examples +/// +/// ``` +/// use std::path::Path; +/// +/// let path = Path::new("/tmp/foo/bar.txt"); +/// +/// for component in path.components() { +/// println!("{:?}", component); +/// } +/// ``` +/// +/// [`path::Path::components`]: struct.Path.html#method.components +#[derive(Clone)] +pub struct Components<'a> { + // The path left to parse components from + path: &'a [u8], + + // The prefix as it was originally parsed, if any + prefix: Option>, + + // true if path *physically* has a root separator; for most Windows + // prefixes, it may have a "logical" rootseparator for the purposes of + // normalization, e.g. \\server\share == \\server\share\. + has_physical_root: bool, + + // The iterator is double-ended, and these two states keep track of what has + // been produced from either end + front: State, + back: State, +} + +/// An iterator over the components of a path, as `OsStr` slices. +#[derive(Clone)] +pub struct Iter<'a> { + inner: Components<'a>, +} + +impl<'a> Components<'a> { + // how long is the prefix, if any? + #[inline] + fn prefix_len(&self) -> usize { + self.prefix.as_ref().map(Prefix::len).unwrap_or(0) + } + + #[inline] + fn prefix_verbatim(&self) -> bool { + self.prefix.as_ref().map(Prefix::is_verbatim).unwrap_or(false) + } + + /// how much of the prefix is left from the point of view of iteration? + #[inline] + fn prefix_remaining(&self) -> usize { + if self.front == State::Prefix { + self.prefix_len() + } else { + 0 + } + } + + // Given the iteration so far, how much of the pre-State::Body path is left? + #[inline] + fn len_before_body(&self) -> usize { + let root = if self.front <= State::StartDir && self.has_physical_root { + 1 + } else { + 0 + }; + let cur_dir = if self.front <= State::StartDir && self.include_cur_dir() { + 1 + } else { + 0 + }; + self.prefix_remaining() + root + cur_dir + } + + // is the iteration complete? + #[inline] + fn finished(&self) -> bool { + self.front == State::Done || self.back == State::Done || self.front > self.back + } + + #[inline] + fn is_sep_byte(&self, b: u8) -> bool { + if self.prefix_verbatim() { + is_verbatim_sep(b) + } else { + is_sep_byte(b) + } + } + + /// Extracts a slice corresponding to the portion of the path remaining for iteration. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let mut components = Path::new("/tmp/foo/bar.txt").components(); + /// components.next(); + /// components.next(); + /// + /// assert_eq!(Path::new("foo/bar.txt"), components.as_path()); + /// ``` + pub fn as_path(&self) -> &'a Path { + let mut comps = self.clone(); + if comps.front == State::Body { + comps.trim_left(); + } + if comps.back == State::Body { + comps.trim_right(); + } + unsafe { Path::from_u8_slice(comps.path) } + } + + /// Is the *original* path rooted? + fn has_root(&self) -> bool { + if self.has_physical_root { + return true; + } + if let Some(p) = self.prefix { + if p.has_implicit_root() { + return true; + } + } + false + } + + /// Should the normalized path include a leading . ? + fn include_cur_dir(&self) -> bool { + if self.has_root() { + return false; + } + let mut iter = self.path[self.prefix_len()..].iter(); + match (iter.next(), iter.next()) { + (Some(&b'.'), None) => true, + (Some(&b'.'), Some(&b)) => self.is_sep_byte(b), + _ => false, + } + } + + // parse a given byte sequence into the corresponding path component + fn parse_single_component<'b>(&self, comp: &'b [u8]) -> Option> { + match comp { + b"." if self.prefix_verbatim() => Some(Component::CurDir), + b"." => None, // . components are normalized away, except at + // the beginning of a path, which is treated + // separately via `include_cur_dir` + b".." => Some(Component::ParentDir), + b"" => None, + _ => Some(Component::Normal(unsafe { u8_slice_as_os_str(comp) })), + } + } + + // parse a component from the left, saying how many bytes to consume to + // remove the component + fn parse_next_component(&self) -> (usize, Option>) { + debug_assert!(self.front == State::Body); + let (extra, comp) = match self.path.iter().position(|b| self.is_sep_byte(*b)) { + None => (0, self.path), + Some(i) => (1, &self.path[..i]), + }; + (comp.len() + extra, self.parse_single_component(comp)) + } + + // parse a component from the right, saying how many bytes to consume to + // remove the component + fn parse_next_component_back(&self) -> (usize, Option>) { + debug_assert!(self.back == State::Body); + let start = self.len_before_body(); + let (extra, comp) = match self.path[start..].iter().rposition(|b| self.is_sep_byte(*b)) { + None => (0, &self.path[start..]), + Some(i) => (1, &self.path[start + i + 1..]), + }; + (comp.len() + extra, self.parse_single_component(comp)) + } + + // trim away repeated separators (i.e. empty components) on the left + fn trim_left(&mut self) { + while !self.path.is_empty() { + let (size, comp) = self.parse_next_component(); + if comp.is_some() { + return; + } else { + self.path = &self.path[size..]; + } + } + } + + // trim away repeated separators (i.e. empty components) on the right + fn trim_right(&mut self) { + while self.path.len() > self.len_before_body() { + let (size, comp) = self.parse_next_component_back(); + if comp.is_some() { + return; + } else { + self.path = &self.path[..self.path.len() - size]; + } + } + } +} + +impl<'a> AsRef for Components<'a> { + fn as_ref(&self) -> &Path { + self.as_path() + } +} + +impl<'a> AsRef for Components<'a> { + fn as_ref(&self) -> &OsStr { + self.as_path().as_os_str() + } +} + +impl<'a> Iter<'a> { + /// Extracts a slice corresponding to the portion of the path remaining for iteration. + pub fn as_path(&self) -> &'a Path { + self.inner.as_path() + } +} + +impl<'a> AsRef for Iter<'a> { + fn as_ref(&self) -> &Path { + self.as_path() + } +} + +impl<'a> AsRef for Iter<'a> { + fn as_ref(&self) -> &OsStr { + self.as_path().as_os_str() + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = &'a OsStr; + + fn next(&mut self) -> Option<&'a OsStr> { + self.inner.next().map(Component::as_os_str) + } +} + +impl<'a> DoubleEndedIterator for Iter<'a> { + fn next_back(&mut self) -> Option<&'a OsStr> { + self.inner.next_back().map(Component::as_os_str) + } +} + +impl<'a> Iterator for Components<'a> { + type Item = Component<'a>; + + fn next(&mut self) -> Option> { + while !self.finished() { + match self.front { + State::Prefix if self.prefix_len() > 0 => { + self.front = State::StartDir; + debug_assert!(self.prefix_len() <= self.path.len()); + let raw = &self.path[..self.prefix_len()]; + self.path = &self.path[self.prefix_len()..]; + return Some(Component::Prefix(PrefixComponent { + raw: unsafe { u8_slice_as_os_str(raw) }, + parsed: self.prefix.unwrap(), + })); + } + State::Prefix => { + self.front = State::StartDir; + } + State::StartDir => { + self.front = State::Body; + if self.has_physical_root { + debug_assert!(!self.path.is_empty()); + self.path = &self.path[1..]; + return Some(Component::RootDir); + } else if let Some(p) = self.prefix { + if p.has_implicit_root() && !p.is_verbatim() { + return Some(Component::RootDir); + } + } else if self.include_cur_dir() { + debug_assert!(!self.path.is_empty()); + self.path = &self.path[1..]; + return Some(Component::CurDir); + } + } + State::Body if !self.path.is_empty() => { + let (size, comp) = self.parse_next_component(); + self.path = &self.path[size..]; + if comp.is_some() { + return comp; + } + } + State::Body => { + self.front = State::Done; + } + State::Done => unreachable!(), + } + } + None + } +} + +impl<'a> DoubleEndedIterator for Components<'a> { + fn next_back(&mut self) -> Option> { + while !self.finished() { + match self.back { + State::Body if self.path.len() > self.len_before_body() => { + let (size, comp) = self.parse_next_component_back(); + self.path = &self.path[..self.path.len() - size]; + if comp.is_some() { + return comp; + } + } + State::Body => { + self.back = State::StartDir; + } + State::StartDir => { + self.back = State::Prefix; + if self.has_physical_root { + self.path = &self.path[..self.path.len() - 1]; + return Some(Component::RootDir); + } else if let Some(p) = self.prefix { + if p.has_implicit_root() && !p.is_verbatim() { + return Some(Component::RootDir); + } + } else if self.include_cur_dir() { + self.path = &self.path[..self.path.len() - 1]; + return Some(Component::CurDir); + } + } + State::Prefix if self.prefix_len() > 0 => { + self.back = State::Done; + return Some(Component::Prefix(PrefixComponent { + raw: unsafe { u8_slice_as_os_str(self.path) }, + parsed: self.prefix.unwrap(), + })); + } + State::Prefix => { + self.back = State::Done; + return None; + } + State::Done => unreachable!(), + } + } + None + } +} + +impl<'a> cmp::PartialEq for Components<'a> { + fn eq(&self, other: &Components<'a>) -> bool { + Iterator::eq(self.clone(), other.clone()) + } +} + +impl<'a> cmp::Eq for Components<'a> {} + +impl<'a> cmp::PartialOrd for Components<'a> { + fn partial_cmp(&self, other: &Components<'a>) -> Option { + Iterator::partial_cmp(self.clone(), other.clone()) + } +} + +impl<'a> cmp::Ord for Components<'a> { + fn cmp(&self, other: &Components<'a>) -> cmp::Ordering { + Iterator::cmp(self.clone(), other.clone()) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Basic types and traits +//////////////////////////////////////////////////////////////////////////////// + +/// An owned, mutable path (akin to `String`). +/// +/// This type provides methods like `push` and `set_extension` that mutate the +/// path in place. It also implements `Deref` to `Path`, meaning that all +/// methods on `Path` slices are available on `PathBuf` values as well. +/// +/// More details about the overall approach can be found in +/// the module documentation. +/// +/// # Examples +/// +/// ``` +/// use std::path::PathBuf; +/// +/// let mut path = PathBuf::from("c:\\"); +/// path.push("windows"); +/// path.push("system32"); +/// path.set_extension("dll"); +/// ``` +#[derive(Clone)] +pub struct PathBuf { + inner: OsString, +} + +impl PathBuf { + fn as_mut_vec(&mut self) -> &mut Vec { + unsafe { &mut *(self as *mut PathBuf as *mut Vec) } + } + + /// Allocates an empty `PathBuf`. + pub fn new() -> PathBuf { + PathBuf { inner: OsString::new() } + } + + /// Coerces to a `Path` slice. + pub fn as_path(&self) -> &Path { + self + } + + /// Extends `self` with `path`. + /// + /// If `path` is absolute, it replaces the current path. + /// + /// On Windows: + /// + /// * if `path` has a root but no prefix (e.g. `\windows`), it + /// replaces everything except for the prefix (if any) of `self`. + /// * if `path` has a prefix but no root, it replaces `self`. + /// + /// # Examples + /// + /// ``` + /// use std::path::PathBuf; + /// + /// let mut path = PathBuf::new(); + /// path.push("/tmp"); + /// path.push("file.bk"); + /// assert_eq!(path, PathBuf::from("/tmp/file.bk")); + /// + /// // Pushing an absolute path replaces the current path + /// path.push("/etc/passwd"); + /// assert_eq!(path, PathBuf::from("/etc/passwd")); + /// ``` + pub fn push>(&mut self, path: P) { + self._push(path.as_ref()) + } + + fn _push(&mut self, path: &Path) { + // in general, a separator is needed if the rightmost byte is not a separator + let mut need_sep = self.as_mut_vec().last().map(|c| !is_sep_byte(*c)).unwrap_or(false); + + // in the special case of `C:` on Windows, do *not* add a separator + { + let comps = self.components(); + if comps.prefix_len() > 0 && comps.prefix_len() == comps.path.len() && + comps.prefix.unwrap().is_drive() { + need_sep = false + } + } + + // absolute `path` replaces `self` + if path.is_absolute() || path.prefix().is_some() { + self.as_mut_vec().truncate(0); + + // `path` has a root but no prefix, e.g. `\windows` (Windows only) + } else if path.has_root() { + let prefix_len = self.components().prefix_remaining(); + self.as_mut_vec().truncate(prefix_len); + + // `path` is a pure relative path + } else if need_sep { + self.inner.push(MAIN_SEP_STR); + } + + self.inner.push(path); + } + + /// Truncate `self` to `self.parent()`. + /// + /// Returns false and does nothing if `self.file_name()` is `None`. + /// Otherwise, returns `true`. + pub fn pop(&mut self) -> bool { + match self.parent().map(|p| p.as_u8_slice().len()) { + Some(len) => { + self.as_mut_vec().truncate(len); + true + } + None => false, + } + } + + /// Updates `self.file_name()` to `file_name`. + /// + /// If `self.file_name()` was `None`, this is equivalent to pushing + /// `file_name`. + /// + /// # Examples + /// + /// ``` + /// use std::path::PathBuf; + /// + /// let mut buf = PathBuf::from("/"); + /// assert!(buf.file_name() == None); + /// buf.set_file_name("bar"); + /// assert!(buf == PathBuf::from("/bar")); + /// assert!(buf.file_name().is_some()); + /// buf.set_file_name("baz.txt"); + /// assert!(buf == PathBuf::from("/baz.txt")); + /// ``` + pub fn set_file_name>(&mut self, file_name: S) { + self._set_file_name(file_name.as_ref()) + } + + fn _set_file_name(&mut self, file_name: &OsStr) { + if self.file_name().is_some() { + let popped = self.pop(); + debug_assert!(popped); + } + self.push(file_name); + } + + /// Updates `self.extension()` to `extension`. + /// + /// If `self.file_name()` is `None`, does nothing and returns `false`. + /// + /// Otherwise, returns `true`; if `self.extension()` is `None`, the extension + /// is added; otherwise it is replaced. + pub fn set_extension>(&mut self, extension: S) -> bool { + self._set_extension(extension.as_ref()) + } + + fn _set_extension(&mut self, extension: &OsStr) -> bool { + if self.file_name().is_none() { + return false; + } + + let mut stem = match self.file_stem() { + Some(stem) => stem.to_os_string(), + None => OsString::new(), + }; + + if !os_str_as_u8_slice(extension).is_empty() { + stem.push("."); + stem.push(extension); + } + self.set_file_name(&stem); + + true + } + + /// Consumes the `PathBuf`, yielding its internal `OsString` storage. + pub fn into_os_string(self) -> OsString { + self.inner + } +} + +impl<'a, T: ?Sized + AsRef> From<&'a T> for PathBuf { + fn from(s: &'a T) -> PathBuf { + PathBuf::from(s.as_ref().to_os_string()) + } +} + +impl From for PathBuf { + fn from(s: OsString) -> PathBuf { + PathBuf { inner: s } + } +} + +impl From for PathBuf { + fn from(s: String) -> PathBuf { + PathBuf::from(OsString::from(s)) + } +} + +impl> iter::FromIterator

for PathBuf { + fn from_iter>(iter: I) -> PathBuf { + let mut buf = PathBuf::new(); + buf.extend(iter); + buf + } +} + +impl> iter::Extend

for PathBuf { + fn extend>(&mut self, iter: I) { + for p in iter { + self.push(p.as_ref()) + } + } +} + +impl fmt::Debug for PathBuf { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&**self, formatter) + } +} + +impl ops::Deref for PathBuf { + type Target = Path; + + fn deref(&self) -> &Path { + Path::new(&self.inner) + } +} + +impl Borrow for PathBuf { + fn borrow(&self) -> &Path { + self.deref() + } +} + +impl<'a> From<&'a Path> for Cow<'a, Path> { + #[inline] + fn from(s: &'a Path) -> Cow<'a, Path> { + Cow::Borrowed(s) + } +} + +impl<'a> From for Cow<'a, Path> { + #[inline] + fn from(s: PathBuf) -> Cow<'a, Path> { + Cow::Owned(s) + } +} + +impl ToOwned for Path { + type Owned = PathBuf; + fn to_owned(&self) -> PathBuf { + self.to_path_buf() + } +} + +impl cmp::PartialEq for PathBuf { + fn eq(&self, other: &PathBuf) -> bool { + self.components() == other.components() + } +} + +impl Hash for PathBuf { + fn hash(&self, h: &mut H) { + self.as_path().hash(h) + } +} + +impl cmp::Eq for PathBuf {} + +impl cmp::PartialOrd for PathBuf { + fn partial_cmp(&self, other: &PathBuf) -> Option { + self.components().partial_cmp(other.components()) + } +} + +impl cmp::Ord for PathBuf { + fn cmp(&self, other: &PathBuf) -> cmp::Ordering { + self.components().cmp(other.components()) + } +} + +impl AsRef for PathBuf { + fn as_ref(&self) -> &OsStr { + &self.inner[..] + } +} + +impl Into for PathBuf { + fn into(self) -> OsString { + self.inner + } +} + +/// A slice of a path (akin to `str`). +/// +/// This type supports a number of operations for inspecting a path, including +/// breaking the path into its components (separated by `/` or `\`, depending on +/// the platform), extracting the file name, determining whether the path is +/// absolute, and so on. More details about the overall approach can be found in +/// the module documentation. +/// +/// This is an *unsized* type, meaning that it must always be used behind a +/// pointer like `&` or `Box`. +/// +/// # Examples +/// +/// ``` +/// use std::path::Path; +/// +/// let path = Path::new("/tmp/foo/bar.txt"); +/// let file = path.file_name(); +/// let extension = path.extension(); +/// let parent_dir = path.parent(); +/// ``` +/// +pub struct Path { + inner: OsStr, +} + +/// An error returned from the `Path::strip_prefix` method indicating that the +/// prefix was not found in `self`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StripPrefixError(()); + +impl Path { + // The following (private!) function allows construction of a path from a u8 + // slice, which is only safe when it is known to follow the OsStr encoding. + unsafe fn from_u8_slice(s: &[u8]) -> &Path { + Path::new(u8_slice_as_os_str(s)) + } + // The following (private!) function reveals the byte encoding used for OsStr. + fn as_u8_slice(&self) -> &[u8] { + os_str_as_u8_slice(&self.inner) + } + + /// Directly wrap a string slice as a `Path` slice. + /// + /// This is a cost-free conversion. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// Path::new("foo.txt"); + /// ``` + /// + /// You can create `Path`s from `String`s, or even other `Path`s: + /// + /// ``` + /// use std::path::Path; + /// + /// let string = String::from("foo.txt"); + /// let from_string = Path::new(&string); + /// let from_path = Path::new(&from_string); + /// assert_eq!(from_string, from_path); + /// ``` + pub fn new + ?Sized>(s: &S) -> &Path { + unsafe { mem::transmute(s.as_ref()) } + } + + /// Yields the underlying `OsStr` slice. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let os_str = Path::new("foo.txt").as_os_str(); + /// assert_eq!(os_str, std::ffi::OsStr::new("foo.txt")); + /// ``` + pub fn as_os_str(&self) -> &OsStr { + &self.inner + } + + /// Yields a `&str` slice if the `Path` is valid unicode. + /// + /// This conversion may entail doing a check for UTF-8 validity. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path_str = Path::new("foo.txt").to_str(); + /// assert_eq!(path_str, Some("foo.txt")); + /// ``` + pub fn to_str(&self) -> Option<&str> { + self.inner.to_str() + } + + /// Converts a `Path` to a `Cow`. + /// + /// Any non-Unicode sequences are replaced with U+FFFD REPLACEMENT CHARACTER. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path_str = Path::new("foo.txt").to_string_lossy(); + /// assert_eq!(path_str, "foo.txt"); + /// ``` + pub fn to_string_lossy(&self) -> Cow { + self.inner.to_string_lossy() + } + + /// Converts a `Path` to an owned `PathBuf`. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path_buf = Path::new("foo.txt").to_path_buf(); + /// assert_eq!(path_buf, std::path::PathBuf::from("foo.txt")); + /// ``` + pub fn to_path_buf(&self) -> PathBuf { + PathBuf::from(self.inner.to_os_string()) + } + + /// A path is *absolute* if it is independent of the current directory. + /// + /// * On Unix, a path is absolute if it starts with the root, so + /// `is_absolute` and `has_root` are equivalent. + /// + /// * On Windows, a path is absolute if it has a prefix and starts with the + /// root: `c:\windows` is absolute, while `c:temp` and `\temp` are not. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// assert!(!Path::new("foo.txt").is_absolute()); + /// ``` + #[allow(deprecated)] + pub fn is_absolute(&self) -> bool { + self.has_root() && (cfg!(unix) || self.prefix().is_some()) + } + + /// A path is *relative* if it is not absolute. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// assert!(Path::new("foo.txt").is_relative()); + /// ``` + pub fn is_relative(&self) -> bool { + !self.is_absolute() + } + + fn prefix(&self) -> Option { + self.components().prefix + } + + /// A path has a root if the body of the path begins with the directory separator. + /// + /// * On Unix, a path has a root if it begins with `/`. + /// + /// * On Windows, a path has a root if it: + /// * has no prefix and begins with a separator, e.g. `\\windows` + /// * has a prefix followed by a separator, e.g. `c:\windows` but not `c:windows` + /// * has any non-disk prefix, e.g. `\\server\share` + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// assert!(Path::new("/etc/passwd").has_root()); + /// ``` + pub fn has_root(&self) -> bool { + self.components().has_root() + } + + /// The path without its final component, if any. + /// + /// Returns `None` if the path terminates in a root or prefix. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path = Path::new("/foo/bar"); + /// let parent = path.parent().unwrap(); + /// assert_eq!(parent, Path::new("/foo")); + /// + /// let grand_parent = parent.parent().unwrap(); + /// assert_eq!(grand_parent, Path::new("/")); + /// assert_eq!(grand_parent.parent(), None); + /// ``` + pub fn parent(&self) -> Option<&Path> { + let mut comps = self.components(); + let comp = comps.next_back(); + comp.and_then(|p| { + match p { + Component::Normal(_) | + Component::CurDir | + Component::ParentDir => Some(comps.as_path()), + _ => None, + } + }) + } + + /// The final component of the path, if it is a normal file. + /// + /// If the path terminates in `..`, `file_name` will return `None`. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// use std::ffi::OsStr; + /// + /// let path = Path::new("foo.txt"); + /// let os_str = OsStr::new("foo.txt"); + /// + /// assert_eq!(Some(os_str), path.file_name()); + /// ``` + /// + /// # Other examples + /// + /// ``` + /// use std::path::Path; + /// use std::ffi::OsStr; + /// + /// assert_eq!(Some(OsStr::new("foo.txt")), Path::new("foo.txt/.").file_name()); + /// assert_eq!(Some(OsStr::new("foo.txt")), Path::new("foo.txt/.//").file_name()); + /// assert_eq!(None, Path::new("foo.txt/..").file_name()); + /// ``` + pub fn file_name(&self) -> Option<&OsStr> { + self.components().next_back().and_then(|p| { + match p { + Component::Normal(p) => Some(p.as_ref()), + _ => None, + } + }) + } + + /// Returns a path that, when joined onto `base`, yields `self`. + /// + /// # Errors + /// + /// If `base` is not a prefix of `self` (i.e. `starts_with` + /// returns `false`), returns `Err`. + pub fn strip_prefix<'a, P: ?Sized>(&'a self, base: &'a P) + -> Result<&'a Path, StripPrefixError> + where P: AsRef + { + self._strip_prefix(base.as_ref()) + } + + fn _strip_prefix<'a>(&'a self, base: &'a Path) + -> Result<&'a Path, StripPrefixError> { + iter_after(self.components(), base.components()) + .map(|c| c.as_path()) + .ok_or(StripPrefixError(())) + } + + /// Determines whether `base` is a prefix of `self`. + /// + /// Only considers whole path components to match. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path = Path::new("/etc/passwd"); + /// + /// assert!(path.starts_with("/etc")); + /// + /// assert!(!path.starts_with("/e")); + /// ``` + pub fn starts_with>(&self, base: P) -> bool { + self._starts_with(base.as_ref()) + } + + fn _starts_with(&self, base: &Path) -> bool { + iter_after(self.components(), base.components()).is_some() + } + + /// Determines whether `child` is a suffix of `self`. + /// + /// Only considers whole path components to match. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path = Path::new("/etc/passwd"); + /// + /// assert!(path.ends_with("passwd")); + /// ``` + pub fn ends_with>(&self, child: P) -> bool { + self._ends_with(child.as_ref()) + } + + fn _ends_with(&self, child: &Path) -> bool { + iter_after(self.components().rev(), child.components().rev()).is_some() + } + + /// Extracts the stem (non-extension) portion of `self.file_name()`. + /// + /// The stem is: + /// + /// * None, if there is no file name; + /// * The entire file name if there is no embedded `.`; + /// * The entire file name if the file name begins with `.` and has no other `.`s within; + /// * Otherwise, the portion of the file name before the final `.` + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path = Path::new("foo.rs"); + /// + /// assert_eq!("foo", path.file_stem().unwrap()); + /// ``` + pub fn file_stem(&self) -> Option<&OsStr> { + self.file_name().map(split_file_at_dot).and_then(|(before, after)| before.or(after)) + } + + /// Extracts the extension of `self.file_name()`, if possible. + /// + /// The extension is: + /// + /// * None, if there is no file name; + /// * None, if there is no embedded `.`; + /// * None, if the file name begins with `.` and has no other `.`s within; + /// * Otherwise, the portion of the file name after the final `.` + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path = Path::new("foo.rs"); + /// + /// assert_eq!("rs", path.extension().unwrap()); + /// ``` + pub fn extension(&self) -> Option<&OsStr> { + self.file_name().map(split_file_at_dot).and_then(|(before, after)| before.and(after)) + } + + /// Creates an owned `PathBuf` with `path` adjoined to `self`. + /// + /// See `PathBuf::push` for more details on what it means to adjoin a path. + /// + /// # Examples + /// + /// ``` + /// use std::path::{Path, PathBuf}; + /// + /// assert_eq!(Path::new("/etc").join("passwd"), PathBuf::from("/etc/passwd")); + /// ``` + pub fn join>(&self, path: P) -> PathBuf { + self._join(path.as_ref()) + } + + fn _join(&self, path: &Path) -> PathBuf { + let mut buf = self.to_path_buf(); + buf.push(path); + buf + } + + /// Creates an owned `PathBuf` like `self` but with the given file name. + /// + /// See `PathBuf::set_file_name` for more details. + /// + /// # Examples + /// + /// ``` + /// use std::path::{Path, PathBuf}; + /// + /// let path = Path::new("/tmp/foo.txt"); + /// assert_eq!(path.with_file_name("bar.txt"), PathBuf::from("/tmp/bar.txt")); + /// ``` + pub fn with_file_name>(&self, file_name: S) -> PathBuf { + self._with_file_name(file_name.as_ref()) + } + + fn _with_file_name(&self, file_name: &OsStr) -> PathBuf { + let mut buf = self.to_path_buf(); + buf.set_file_name(file_name); + buf + } + + /// Creates an owned `PathBuf` like `self` but with the given extension. + /// + /// See `PathBuf::set_extension` for more details. + /// + /// # Examples + /// + /// ``` + /// use std::path::{Path, PathBuf}; + /// + /// let path = Path::new("foo.rs"); + /// assert_eq!(path.with_extension("txt"), PathBuf::from("foo.txt")); + /// ``` + pub fn with_extension>(&self, extension: S) -> PathBuf { + self._with_extension(extension.as_ref()) + } + + fn _with_extension(&self, extension: &OsStr) -> PathBuf { + let mut buf = self.to_path_buf(); + buf.set_extension(extension); + buf + } + + /// Produce an iterator over the components of the path. + /// + /// # Examples + /// + /// ``` + /// use std::path::{Path, Component}; + /// use std::ffi::OsStr; + /// + /// let mut components = Path::new("/tmp/foo.txt").components(); + /// + /// assert_eq!(components.next(), Some(Component::RootDir)); + /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("tmp")))); + /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("foo.txt")))); + /// assert_eq!(components.next(), None) + /// ``` + pub fn components(&self) -> Components { + let prefix = parse_prefix(self.as_os_str()); + Components { + path: self.as_u8_slice(), + prefix: prefix, + has_physical_root: has_physical_root(self.as_u8_slice(), prefix), + front: State::Prefix, + back: State::Body, + } + } + + /// Produce an iterator over the path's components viewed as `OsStr` slices. + /// + /// # Examples + /// + /// ``` + /// use std::path::{self, Path}; + /// use std::ffi::OsStr; + /// + /// let mut it = Path::new("/tmp/foo.txt").iter(); + /// assert_eq!(it.next(), Some(OsStr::new(&path::MAIN_SEPARATOR.to_string()))); + /// assert_eq!(it.next(), Some(OsStr::new("tmp"))); + /// assert_eq!(it.next(), Some(OsStr::new("foo.txt"))); + /// assert_eq!(it.next(), None) + /// ``` + pub fn iter(&self) -> Iter { + Iter { inner: self.components() } + } + + /// Returns an object that implements `Display` for safely printing paths + /// that may contain non-Unicode data. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// + /// let path = Path::new("/tmp/foo.rs"); + /// + /// println!("{}", path.display()); + /// ``` + pub fn display(&self) -> Display { + Display { path: self } + } + + + //NOTE: The following functions rely on filesystem functionality that + //probably have to be implemented in ctru-rs instead of this library, + //and thus are commented out + + /* + /// Query the file system to get information about a file, directory, etc. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. + /// + /// This is an alias to [`fs::metadata`]. + /// + /// [`fs::metadata`]: ../fs/fn.metadata.html + pub fn metadata(&self) -> io::Result { + fs::metadata(self) + } + + /// Query the metadata about a file without following symlinks. + /// + /// This is an alias to [`fs::symlink_metadata`]. + /// + /// [`fs::symlink_metadata`]: ../fs/fn.symlink_metadata.html + + pub fn symlink_metadata(&self) -> io::Result { + fs::symlink_metadata(self) + } + + + /// Returns the canonical form of the path with all intermediate components + /// normalized and symbolic links resolved. + /// + /// This is an alias to [`fs::canonicalize`]. + /// + /// [`fs::canonicalize`]: ../fs/fn.canonicalize.html + + pub fn canonicalize(&self) -> io::Result { + fs::canonicalize(self) + } + + + /// Reads a symbolic link, returning the file that the link points to. + /// + /// This is an alias to [`fs::read_link`]. + /// + /// [`fs::read_link`]: ../fs/fn.read_link.html + + pub fn read_link(&self) -> io::Result { + fs::read_link(self) + } + + + /// Returns an iterator over the entries within a directory. + /// + /// The iterator will yield instances of `io::Result`. New errors may + /// be encountered after an iterator is initially constructed. + /// + /// This is an alias to [`fs::read_dir`]. + /// + /// [`fs::read_dir`]: ../fs/fn.read_dir.html + + pub fn read_dir(&self) -> io::Result { + fs::read_dir(self) + } + + + /// Returns whether the path points at an existing entity. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. + /// + /// # Examples + /// + /// ```no_run + /// use std::path::Path; + /// assert_eq!(Path::new("does_not_exist.txt").exists(), false); + /// ``` + + pub fn exists(&self) -> bool { + fs::metadata(self).is_ok() + } + + + /// Returns whether the path is pointing at a regular file. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. + /// + /// # Examples + /// + /// ```no_run + /// use std::path::Path; + /// assert_eq!(Path::new("./is_a_directory/").is_file(), false); + /// assert_eq!(Path::new("a_file.txt").is_file(), true); + /// ``` + + pub fn is_file(&self) -> bool { + fs::metadata(self).map(|m| m.is_file()).unwrap_or(false) + } + + + /// Returns whether the path is pointing at a directory. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. + /// + /// # Examples + /// + /// ```no_run + /// use std::path::Path; + /// assert_eq!(Path::new("./is_a_directory/").is_dir(), true); + /// assert_eq!(Path::new("a_file.txt").is_dir(), false); + /// ``` + + pub fn is_dir(&self) -> bool { + fs::metadata(self).map(|m| m.is_dir()).unwrap_or(false) + } + */ +} + +impl AsRef for Path { + fn as_ref(&self) -> &OsStr { + &self.inner + } +} + +impl fmt::Debug for Path { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.inner.fmt(formatter) + } +} + +/// Helper struct for safely printing paths with `format!()` and `{}` +pub struct Display<'a> { + path: &'a Path, +} + +impl<'a> fmt::Debug for Display<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.path.to_string_lossy(), f) + } +} + +impl<'a> fmt::Display for Display<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.path.to_string_lossy(), f) + } +} + +impl cmp::PartialEq for Path { + fn eq(&self, other: &Path) -> bool { + self.components().eq(other.components()) + } +} + +impl Hash for Path { + fn hash(&self, h: &mut H) { + for component in self.components() { + component.hash(h); + } + } +} + +impl cmp::Eq for Path {} + +impl cmp::PartialOrd for Path { + fn partial_cmp(&self, other: &Path) -> Option { + self.components().partial_cmp(other.components()) + } +} + +impl cmp::Ord for Path { + fn cmp(&self, other: &Path) -> cmp::Ordering { + self.components().cmp(other.components()) + } +} + +impl AsRef for Path { + fn as_ref(&self) -> &Path { + self + } +} + +impl AsRef for OsStr { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl<'a> AsRef for Cow<'a, OsStr> { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for OsString { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for str { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for String { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for PathBuf { + fn as_ref(&self) -> &Path { + self + } +} + +impl<'a> IntoIterator for &'a PathBuf { + type Item = &'a OsStr; + type IntoIter = Iter<'a>; + fn into_iter(self) -> Iter<'a> { self.iter() } +} + +impl<'a> IntoIterator for &'a Path { + type Item = &'a OsStr; + type IntoIter = Iter<'a>; + fn into_iter(self) -> Iter<'a> { self.iter() } +} + +macro_rules! impl_cmp { + ($lhs:ty, $rhs: ty) => { + impl<'a, 'b> PartialEq<$rhs> for $lhs { + #[inline] + fn eq(&self, other: &$rhs) -> bool { ::eq(self, other) } + } + + impl<'a, 'b> PartialEq<$lhs> for $rhs { + #[inline] + fn eq(&self, other: &$lhs) -> bool { ::eq(self, other) } + } + + impl<'a, 'b> PartialOrd<$rhs> for $lhs { + #[inline] + fn partial_cmp(&self, other: &$rhs) -> Option { + ::partial_cmp(self, other) + } + } + + impl<'a, 'b> PartialOrd<$lhs> for $rhs { + #[inline] + fn partial_cmp(&self, other: &$lhs) -> Option { + ::partial_cmp(self, other) + } + } + } +} + +impl_cmp!(PathBuf, Path); +impl_cmp!(PathBuf, &'a Path); +impl_cmp!(Cow<'a, Path>, Path); +impl_cmp!(Cow<'a, Path>, &'b Path); +impl_cmp!(Cow<'a, Path>, PathBuf); + +macro_rules! impl_cmp_os_str { + ($lhs:ty, $rhs: ty) => { + impl<'a, 'b> PartialEq<$rhs> for $lhs { + #[inline] + fn eq(&self, other: &$rhs) -> bool { ::eq(self, other.as_ref()) } + } + + impl<'a, 'b> PartialEq<$lhs> for $rhs { + #[inline] + fn eq(&self, other: &$lhs) -> bool { ::eq(self.as_ref(), other) } + } + + impl<'a, 'b> PartialOrd<$rhs> for $lhs { + #[inline] + fn partial_cmp(&self, other: &$rhs) -> Option { + ::partial_cmp(self, other.as_ref()) + } + } + + impl<'a, 'b> PartialOrd<$lhs> for $rhs { + #[inline] + fn partial_cmp(&self, other: &$lhs) -> Option { + ::partial_cmp(self.as_ref(), other) + } + } + } +} + +impl_cmp_os_str!(PathBuf, OsStr); +impl_cmp_os_str!(PathBuf, &'a OsStr); +impl_cmp_os_str!(PathBuf, Cow<'a, OsStr>); +impl_cmp_os_str!(PathBuf, OsString); +impl_cmp_os_str!(Path, OsStr); +impl_cmp_os_str!(Path, &'a OsStr); +impl_cmp_os_str!(Path, Cow<'a, OsStr>); +impl_cmp_os_str!(Path, OsString); +impl_cmp_os_str!(&'a Path, OsStr); +impl_cmp_os_str!(&'a Path, Cow<'b, OsStr>); +impl_cmp_os_str!(&'a Path, OsString); +impl_cmp_os_str!(Cow<'a, Path>, OsStr); +impl_cmp_os_str!(Cow<'a, Path>, &'b OsStr); +impl_cmp_os_str!(Cow<'a, Path>, OsString); + +/* +impl fmt::Display for StripPrefixError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.description().fmt(f) + } +} +*/ + +/* +impl Error for StripPrefixError { + fn description(&self) -> &str { "prefix not found" } +} +*/ + +#[cfg(test)] +mod tests { + use super::*; + use collections::string::{ToString, String}; + use collections::borrow; + use collections::Vec; + + macro_rules! t( + ($path:expr, iter: $iter:expr) => ( + { + let path = Path::new($path); + + // Forward iteration + let comps = path.iter() + .map(|p| p.to_string_lossy().into_owned()) + .collect::>(); + let exp: &[&str] = &$iter; + let exps = exp.iter().map(|s| s.to_string()).collect::>(); + assert!(comps == exps, "iter: Expected {:?}, found {:?}", + exps, comps); + + // Reverse iteration + let comps = Path::new($path).iter().rev() + .map(|p| p.to_string_lossy().into_owned()) + .collect::>(); + let exps = exps.into_iter().rev().collect::>(); + assert!(comps == exps, "iter().rev(): Expected {:?}, found {:?}", + exps, comps); + } + ); + + ($path:expr, has_root: $has_root:expr, is_absolute: $is_absolute:expr) => ( + { + let path = Path::new($path); + + let act_root = path.has_root(); + assert!(act_root == $has_root, "has_root: Expected {:?}, found {:?}", + $has_root, act_root); + + let act_abs = path.is_absolute(); + assert!(act_abs == $is_absolute, "is_absolute: Expected {:?}, found {:?}", + $is_absolute, act_abs); + } + ); + + ($path:expr, parent: $parent:expr, file_name: $file:expr) => ( + { + let path = Path::new($path); + + let parent = path.parent().map(|p| p.to_str().unwrap()); + let exp_parent: Option<&str> = $parent; + assert!(parent == exp_parent, "parent: Expected {:?}, found {:?}", + exp_parent, parent); + + let file = path.file_name().map(|p| p.to_str().unwrap()); + let exp_file: Option<&str> = $file; + assert!(file == exp_file, "file_name: Expected {:?}, found {:?}", + exp_file, file); + } + ); + + ($path:expr, file_stem: $file_stem:expr, extension: $extension:expr) => ( + { + let path = Path::new($path); + + let stem = path.file_stem().map(|p| p.to_str().unwrap()); + let exp_stem: Option<&str> = $file_stem; + assert!(stem == exp_stem, "file_stem: Expected {:?}, found {:?}", + exp_stem, stem); + + let ext = path.extension().map(|p| p.to_str().unwrap()); + let exp_ext: Option<&str> = $extension; + assert!(ext == exp_ext, "extension: Expected {:?}, found {:?}", + exp_ext, ext); + } + ); + + ($path:expr, iter: $iter:expr, + has_root: $has_root:expr, is_absolute: $is_absolute:expr, + parent: $parent:expr, file_name: $file:expr, + file_stem: $file_stem:expr, extension: $extension:expr) => ( + { + t!($path, iter: $iter); + t!($path, has_root: $has_root, is_absolute: $is_absolute); + t!($path, parent: $parent, file_name: $file); + t!($path, file_stem: $file_stem, extension: $extension); + } + ); + ); + + #[test] + fn into() { + use collections::borrow::Cow; + + let static_path = Path::new("/home/foo"); + let static_cow_path: Cow<'static, Path> = static_path.into(); + let pathbuf = PathBuf::from("/home/foo"); + + { + let path: &Path = &pathbuf; + let borrowed_cow_path: Cow = path.into(); + + assert_eq!(static_cow_path, borrowed_cow_path); + } + + let owned_cow_path: Cow<'static, Path> = pathbuf.into(); + + assert_eq!(static_cow_path, owned_cow_path); + } + + #[test] + #[cfg(unix)] + pub fn test_decompositions_unix() { + t!("", + iter: [], + has_root: false, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("/", + iter: ["/"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("/foo", + iter: ["/", "foo"], + has_root: true, + is_absolute: true, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("/foo/", + iter: ["/", "foo"], + has_root: true, + is_absolute: true, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/bar", + iter: ["foo", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("/foo/bar", + iter: ["/", "foo", "bar"], + has_root: true, + is_absolute: true, + parent: Some("/foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("///foo///", + iter: ["/", "foo"], + has_root: true, + is_absolute: true, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("///foo///bar", + iter: ["/", "foo", "bar"], + has_root: true, + is_absolute: true, + parent: Some("///foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("./.", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("/..", + iter: ["/", ".."], + has_root: true, + is_absolute: true, + parent: Some("/"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("../", + iter: [".."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/.", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/..", + iter: ["foo", ".."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/./", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/./bar", + iter: ["foo", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("foo/../", + iter: ["foo", ".."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/../bar", + iter: ["foo", "..", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo/.."), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("./a", + iter: [".", "a"], + has_root: false, + is_absolute: false, + parent: Some("."), + file_name: Some("a"), + file_stem: Some("a"), + extension: None + ); + + t!(".", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("./", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("a/b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a//b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a/./b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a/b/c", + iter: ["a", "b", "c"], + has_root: false, + is_absolute: false, + parent: Some("a/b"), + file_name: Some("c"), + file_stem: Some("c"), + extension: None + ); + + t!(".foo", + iter: [".foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some(".foo"), + file_stem: Some(".foo"), + extension: None + ); + } + + #[test] + #[cfg(windows)] + pub fn test_decompositions_windows() { + t!("", + iter: [], + has_root: false, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("/", + iter: ["\\"], + has_root: true, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\", + iter: ["\\"], + has_root: true, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("c:", + iter: ["c:"], + has_root: false, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("c:\\", + iter: ["c:", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("c:/", + iter: ["c:", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("/foo", + iter: ["\\", "foo"], + has_root: true, + is_absolute: false, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("/foo/", + iter: ["\\", "foo"], + has_root: true, + is_absolute: false, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/bar", + iter: ["foo", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("/foo/bar", + iter: ["\\", "foo", "bar"], + has_root: true, + is_absolute: false, + parent: Some("/foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("///foo///", + iter: ["\\", "foo"], + has_root: true, + is_absolute: false, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("///foo///bar", + iter: ["\\", "foo", "bar"], + has_root: true, + is_absolute: false, + parent: Some("///foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("./.", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("/..", + iter: ["\\", ".."], + has_root: true, + is_absolute: false, + parent: Some("/"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("../", + iter: [".."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/.", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/..", + iter: ["foo", ".."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/./", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/./bar", + iter: ["foo", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("foo/../", + iter: ["foo", ".."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/../bar", + iter: ["foo", "..", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo/.."), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("./a", + iter: [".", "a"], + has_root: false, + is_absolute: false, + parent: Some("."), + file_name: Some("a"), + file_stem: Some("a"), + extension: None + ); + + t!(".", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("./", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None + ); + + t!("a/b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a//b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a/./b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a/b/c", + iter: ["a", "b", "c"], + has_root: false, + is_absolute: false, + parent: Some("a/b"), + file_name: Some("c"), + file_stem: Some("c"), + extension: None); + + t!("a\\b\\c", + iter: ["a", "b", "c"], + has_root: false, + is_absolute: false, + parent: Some("a\\b"), + file_name: Some("c"), + file_stem: Some("c"), + extension: None + ); + + t!("\\a", + iter: ["\\", "a"], + has_root: true, + is_absolute: false, + parent: Some("\\"), + file_name: Some("a"), + file_stem: Some("a"), + extension: None + ); + + t!("c:\\foo.txt", + iter: ["c:", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("c:\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("\\\\server\\share\\foo.txt", + iter: ["\\\\server\\share", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\server\\share\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("\\\\server\\share", + iter: ["\\\\server\\share", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\server", + iter: ["\\", "server"], + has_root: true, + is_absolute: false, + parent: Some("\\"), + file_name: Some("server"), + file_stem: Some("server"), + extension: None + ); + + t!("\\\\?\\bar\\foo.txt", + iter: ["\\\\?\\bar", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\bar\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("\\\\?\\bar", + iter: ["\\\\?\\bar"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\", + iter: ["\\\\?\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\UNC\\server\\share\\foo.txt", + iter: ["\\\\?\\UNC\\server\\share", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\UNC\\server\\share\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("\\\\?\\UNC\\server", + iter: ["\\\\?\\UNC\\server"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\UNC\\", + iter: ["\\\\?\\UNC\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\C:\\foo.txt", + iter: ["\\\\?\\C:", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\C:\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + + t!("\\\\?\\C:\\", + iter: ["\\\\?\\C:", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\?\\C:", + iter: ["\\\\?\\C:"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\?\\foo/bar", + iter: ["\\\\?\\foo/bar"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\?\\C:/foo", + iter: ["\\\\?\\C:/foo"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\.\\foo\\bar", + iter: ["\\\\.\\foo", "\\", "bar"], + has_root: true, + is_absolute: true, + parent: Some("\\\\.\\foo\\"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + + t!("\\\\.\\foo", + iter: ["\\\\.\\foo", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\.\\foo/bar", + iter: ["\\\\.\\foo/bar", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\.\\foo\\bar/baz", + iter: ["\\\\.\\foo", "\\", "bar", "baz"], + has_root: true, + is_absolute: true, + parent: Some("\\\\.\\foo\\bar"), + file_name: Some("baz"), + file_stem: Some("baz"), + extension: None + ); + + + t!("\\\\.\\", + iter: ["\\\\.\\", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\a\\b\\", + iter: ["\\\\?\\a", "\\", "b"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\a\\"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + } + + #[test] + pub fn test_stem_ext() { + t!("foo", + file_stem: Some("foo"), + extension: None + ); + + t!("foo.", + file_stem: Some("foo"), + extension: Some("") + ); + + t!(".foo", + file_stem: Some(".foo"), + extension: None + ); + + t!("foo.txt", + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("foo.bar.txt", + file_stem: Some("foo.bar"), + extension: Some("txt") + ); + + t!("foo.bar.", + file_stem: Some("foo.bar"), + extension: Some("") + ); + + t!(".", + file_stem: None, + extension: None + ); + + t!("..", + file_stem: None, + extension: None + ); + + t!("", + file_stem: None, + extension: None + ); + } + + #[test] + pub fn test_push() { + macro_rules! tp( + ($path:expr, $push:expr, $expected:expr) => ( { + let mut actual = PathBuf::from($path); + actual.push($push); + assert!(actual.to_str() == Some($expected), + "pushing {:?} onto {:?}: Expected {:?}, got {:?}", + $push, $path, $expected, actual.to_str().unwrap()); + }); + ); + + if cfg!(unix) { + tp!("", "foo", "foo"); + tp!("foo", "bar", "foo/bar"); + tp!("foo/", "bar", "foo/bar"); + tp!("foo//", "bar", "foo//bar"); + tp!("foo/.", "bar", "foo/./bar"); + tp!("foo./.", "bar", "foo././bar"); + tp!("foo", "", "foo/"); + tp!("foo", ".", "foo/."); + tp!("foo", "..", "foo/.."); + tp!("foo", "/", "/"); + tp!("/foo/bar", "/", "/"); + tp!("/foo/bar", "/baz", "/baz"); + tp!("/foo/bar", "./baz", "/foo/bar/./baz"); + } else { + tp!("", "foo", "foo"); + tp!("foo", "bar", r"foo\bar"); + tp!("foo/", "bar", r"foo/bar"); + tp!(r"foo\", "bar", r"foo\bar"); + tp!("foo//", "bar", r"foo//bar"); + tp!(r"foo\\", "bar", r"foo\\bar"); + tp!("foo/.", "bar", r"foo/.\bar"); + tp!("foo./.", "bar", r"foo./.\bar"); + tp!(r"foo\.", "bar", r"foo\.\bar"); + tp!(r"foo.\.", "bar", r"foo.\.\bar"); + tp!("foo", "", "foo\\"); + tp!("foo", ".", r"foo\."); + tp!("foo", "..", r"foo\.."); + tp!("foo", "/", "/"); + tp!("foo", r"\", r"\"); + tp!("/foo/bar", "/", "/"); + tp!(r"\foo\bar", r"\", r"\"); + tp!("/foo/bar", "/baz", "/baz"); + tp!("/foo/bar", r"\baz", r"\baz"); + tp!("/foo/bar", "./baz", r"/foo/bar\./baz"); + tp!("/foo/bar", r".\baz", r"/foo/bar\.\baz"); + + tp!("c:\\", "windows", "c:\\windows"); + tp!("c:", "windows", "c:windows"); + + tp!("a\\b\\c", "d", "a\\b\\c\\d"); + tp!("\\a\\b\\c", "d", "\\a\\b\\c\\d"); + tp!("a\\b", "c\\d", "a\\b\\c\\d"); + tp!("a\\b", "\\c\\d", "\\c\\d"); + tp!("a\\b", ".", "a\\b\\."); + tp!("a\\b", "..\\c", "a\\b\\..\\c"); + tp!("a\\b", "C:a.txt", "C:a.txt"); + tp!("a\\b", "C:\\a.txt", "C:\\a.txt"); + tp!("C:\\a", "C:\\b.txt", "C:\\b.txt"); + tp!("C:\\a\\b\\c", "C:d", "C:d"); + tp!("C:a\\b\\c", "C:d", "C:d"); + tp!("C:", r"a\b\c", r"C:a\b\c"); + tp!("C:", r"..\a", r"C:..\a"); + tp!("\\\\server\\share\\foo", + "bar", + "\\\\server\\share\\foo\\bar"); + tp!("\\\\server\\share\\foo", "C:baz", "C:baz"); + tp!("\\\\?\\C:\\a\\b", "C:c\\d", "C:c\\d"); + tp!("\\\\?\\C:a\\b", "C:c\\d", "C:c\\d"); + tp!("\\\\?\\C:\\a\\b", "C:\\c\\d", "C:\\c\\d"); + tp!("\\\\?\\foo\\bar", "baz", "\\\\?\\foo\\bar\\baz"); + tp!("\\\\?\\UNC\\server\\share\\foo", + "bar", + "\\\\?\\UNC\\server\\share\\foo\\bar"); + tp!("\\\\?\\UNC\\server\\share", "C:\\a", "C:\\a"); + tp!("\\\\?\\UNC\\server\\share", "C:a", "C:a"); + + // Note: modified from old path API + tp!("\\\\?\\UNC\\server", "foo", "\\\\?\\UNC\\server\\foo"); + + tp!("C:\\a", + "\\\\?\\UNC\\server\\share", + "\\\\?\\UNC\\server\\share"); + tp!("\\\\.\\foo\\bar", "baz", "\\\\.\\foo\\bar\\baz"); + tp!("\\\\.\\foo\\bar", "C:a", "C:a"); + // again, not sure about the following, but I'm assuming \\.\ should be verbatim + tp!("\\\\.\\foo", "..\\bar", "\\\\.\\foo\\..\\bar"); + + tp!("\\\\?\\C:", "foo", "\\\\?\\C:\\foo"); // this is a weird one + } + } + + #[test] + pub fn test_pop() { + macro_rules! tp( + ($path:expr, $expected:expr, $output:expr) => ( { + let mut actual = PathBuf::from($path); + let output = actual.pop(); + assert!(actual.to_str() == Some($expected) && output == $output, + "popping from {:?}: Expected {:?}/{:?}, got {:?}/{:?}", + $path, $expected, $output, + actual.to_str().unwrap(), output); + }); + ); + + tp!("", "", false); + tp!("/", "/", false); + tp!("foo", "", true); + tp!(".", "", true); + tp!("/foo", "/", true); + tp!("/foo/bar", "/foo", true); + tp!("foo/bar", "foo", true); + tp!("foo/.", "", true); + tp!("foo//bar", "foo", true); + + if cfg!(windows) { + tp!("a\\b\\c", "a\\b", true); + tp!("\\a", "\\", true); + tp!("\\", "\\", false); + + tp!("C:\\a\\b", "C:\\a", true); + tp!("C:\\a", "C:\\", true); + tp!("C:\\", "C:\\", false); + tp!("C:a\\b", "C:a", true); + tp!("C:a", "C:", true); + tp!("C:", "C:", false); + tp!("\\\\server\\share\\a\\b", "\\\\server\\share\\a", true); + tp!("\\\\server\\share\\a", "\\\\server\\share\\", true); + tp!("\\\\server\\share", "\\\\server\\share", false); + tp!("\\\\?\\a\\b\\c", "\\\\?\\a\\b", true); + tp!("\\\\?\\a\\b", "\\\\?\\a\\", true); + tp!("\\\\?\\a", "\\\\?\\a", false); + tp!("\\\\?\\C:\\a\\b", "\\\\?\\C:\\a", true); + tp!("\\\\?\\C:\\a", "\\\\?\\C:\\", true); + tp!("\\\\?\\C:\\", "\\\\?\\C:\\", false); + tp!("\\\\?\\UNC\\server\\share\\a\\b", + "\\\\?\\UNC\\server\\share\\a", + true); + tp!("\\\\?\\UNC\\server\\share\\a", + "\\\\?\\UNC\\server\\share\\", + true); + tp!("\\\\?\\UNC\\server\\share", + "\\\\?\\UNC\\server\\share", + false); + tp!("\\\\.\\a\\b\\c", "\\\\.\\a\\b", true); + tp!("\\\\.\\a\\b", "\\\\.\\a\\", true); + tp!("\\\\.\\a", "\\\\.\\a", false); + + tp!("\\\\?\\a\\b\\", "\\\\?\\a\\", true); + } + } + + #[test] + pub fn test_set_file_name() { + macro_rules! tfn( + ($path:expr, $file:expr, $expected:expr) => ( { + let mut p = PathBuf::from($path); + p.set_file_name($file); + assert!(p.to_str() == Some($expected), + "setting file name of {:?} to {:?}: Expected {:?}, got {:?}", + $path, $file, $expected, + p.to_str().unwrap()); + }); + ); + + tfn!("foo", "foo", "foo"); + tfn!("foo", "bar", "bar"); + tfn!("foo", "", ""); + tfn!("", "foo", "foo"); + if cfg!(unix) { + tfn!(".", "foo", "./foo"); + tfn!("foo/", "bar", "bar"); + tfn!("foo/.", "bar", "bar"); + tfn!("..", "foo", "../foo"); + tfn!("foo/..", "bar", "foo/../bar"); + tfn!("/", "foo", "/foo"); + } else { + tfn!(".", "foo", r".\foo"); + tfn!(r"foo\", "bar", r"bar"); + tfn!(r"foo\.", "bar", r"bar"); + tfn!("..", "foo", r"..\foo"); + tfn!(r"foo\..", "bar", r"foo\..\bar"); + tfn!(r"\", "foo", r"\foo"); + } + } + + #[test] + pub fn test_set_extension() { + macro_rules! tfe( + ($path:expr, $ext:expr, $expected:expr, $output:expr) => ( { + let mut p = PathBuf::from($path); + let output = p.set_extension($ext); + assert!(p.to_str() == Some($expected) && output == $output, + "setting extension of {:?} to {:?}: Expected {:?}/{:?}, got {:?}/{:?}", + $path, $ext, $expected, $output, + p.to_str().unwrap(), output); + }); + ); + + tfe!("foo", "txt", "foo.txt", true); + tfe!("foo.bar", "txt", "foo.txt", true); + tfe!("foo.bar.baz", "txt", "foo.bar.txt", true); + tfe!(".test", "txt", ".test.txt", true); + tfe!("foo.txt", "", "foo", true); + tfe!("foo", "", "foo", true); + tfe!("", "foo", "", false); + tfe!(".", "foo", ".", false); + tfe!("foo/", "bar", "foo.bar", true); + tfe!("foo/.", "bar", "foo.bar", true); + tfe!("..", "foo", "..", false); + tfe!("foo/..", "bar", "foo/..", false); + tfe!("/", "foo", "/", false); + } + + #[test] + fn test_eq_recievers() { + use collections::borrow::Cow; + + let borrowed: &Path = Path::new("foo/bar"); + let mut owned: PathBuf = PathBuf::new(); + owned.push("foo"); + owned.push("bar"); + let borrowed_cow: Cow = borrowed.into(); + let owned_cow: Cow = owned.clone().into(); + + macro_rules! t { + ($($current:expr),+) => { + $( + assert_eq!($current, borrowed); + assert_eq!($current, owned); + assert_eq!($current, borrowed_cow); + assert_eq!($current, owned_cow); + )+ + } + } + + t!(borrowed, owned, borrowed_cow, owned_cow); + } + + #[test] + pub fn test_compare() { + use core::hash::{Hash, Hasher, SipHasher}; + + fn hash(t: T) -> u64 { + let mut s = SipHasher::new_with_keys(0, 0); + t.hash(&mut s); + s.finish() + } + + macro_rules! tc( + ($path1:expr, $path2:expr, eq: $eq:expr, + starts_with: $starts_with:expr, ends_with: $ends_with:expr, + relative_from: $relative_from:expr) => ({ + let path1 = Path::new($path1); + let path2 = Path::new($path2); + + let eq = path1 == path2; + assert!(eq == $eq, "{:?} == {:?}, expected {:?}, got {:?}", + $path1, $path2, $eq, eq); + assert!($eq == (hash(path1) == hash(path2)), + "{:?} == {:?}, expected {:?}, got {} and {}", + $path1, $path2, $eq, hash(path1), hash(path2)); + + let starts_with = path1.starts_with(path2); + assert!(starts_with == $starts_with, + "{:?}.starts_with({:?}), expected {:?}, got {:?}", $path1, $path2, + $starts_with, starts_with); + + let ends_with = path1.ends_with(path2); + assert!(ends_with == $ends_with, + "{:?}.ends_with({:?}), expected {:?}, got {:?}", $path1, $path2, + $ends_with, ends_with); + + let relative_from = path1.strip_prefix(path2) + .map(|p| p.to_str().unwrap()) + .ok(); + let exp: Option<&str> = $relative_from; + assert!(relative_from == exp, + "{:?}.strip_prefix({:?}), expected {:?}, got {:?}", + $path1, $path2, exp, relative_from); + }); + ); + + tc!("", "", + eq: true, + starts_with: true, + ends_with: true, + relative_from: Some("") + ); + + tc!("foo", "", + eq: false, + starts_with: true, + ends_with: true, + relative_from: Some("foo") + ); + + tc!("", "foo", + eq: false, + starts_with: false, + ends_with: false, + relative_from: None + ); + + tc!("foo", "foo", + eq: true, + starts_with: true, + ends_with: true, + relative_from: Some("") + ); + + tc!("foo/", "foo", + eq: true, + starts_with: true, + ends_with: true, + relative_from: Some("") + ); + + tc!("foo/bar", "foo", + eq: false, + starts_with: true, + ends_with: false, + relative_from: Some("bar") + ); + + tc!("foo/bar/baz", "foo/bar", + eq: false, + starts_with: true, + ends_with: false, + relative_from: Some("baz") + ); + + tc!("foo/bar", "foo/bar/baz", + eq: false, + starts_with: false, + ends_with: false, + relative_from: None + ); + + tc!("./foo/bar/", ".", + eq: false, + starts_with: true, + ends_with: false, + relative_from: Some("foo/bar") + ); + + if cfg!(windows) { + tc!(r"C:\src\rust\cargo-test\test\Cargo.toml", + r"c:\src\rust\cargo-test\test", + eq: false, + starts_with: true, + ends_with: false, + relative_from: Some("Cargo.toml") + ); + + tc!(r"c:\foo", r"C:\foo", + eq: true, + starts_with: true, + ends_with: true, + relative_from: Some("") + ); + } + } +} diff --git a/src/sys/mod.rs b/src/sys/mod.rs new file mode 100644 index 0000000..86f49e5 --- /dev/null +++ b/src/sys/mod.rs @@ -0,0 +1,25 @@ +/// A trait for viewing representations from std types +#[doc(hidden)] +pub trait AsInner { + fn as_inner(&self) -> &Inner; +} + +/// A trait for viewing representations from std types +#[doc(hidden)] +pub trait AsInnerMut { + fn as_inner_mut(&mut self) -> &mut Inner; +} + +/// A trait for extracting representations from std types +#[doc(hidden)] +pub trait IntoInner { + fn into_inner(self) -> Inner; +} + +/// A trait for creating std types from internal representations +#[doc(hidden)] +pub trait FromInner { + fn from_inner(inner: Inner) -> Self; +} + +pub mod wtf8; diff --git a/src/sys/wtf8.rs b/src/sys/wtf8.rs new file mode 100644 index 0000000..25c21c7 --- /dev/null +++ b/src/sys/wtf8.rs @@ -0,0 +1,1199 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation of [the WTF-8 encoding](https://simonsapin.github.io/wtf-8/). +//! +//! This library uses Rust’s type system to maintain +//! [well-formedness](https://simonsapin.github.io/wtf-8/#well-formed), +//! like the `String` and `&str` types do for UTF-8. +//! +//! Since [WTF-8 must not be used +//! for interchange](https://simonsapin.github.io/wtf-8/#intended-audience), +//! this library deliberately does not provide access to the underlying bytes +//! of WTF-8 strings, +//! nor can it decode WTF-8 from arbitrary bytes. +//! WTF-8 strings can be obtained from UTF-8, UTF-16, or code points. + +// this module is imported from @SimonSapin's repo and has tons of dead code on +// unix (it's mostly used on windows), so don't worry about dead code here. +#![allow(dead_code)] + +use core::str::next_code_point; + +use ascii::*; +use collections::borrow::Cow; +use rustc_unicode::char; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::iter::FromIterator; +use core::mem; +use core::ops; +use collections::slice; +use core::str; +use collections::String; +use sys::AsInner; +use collections::Vec; + +const UTF8_REPLACEMENT_CHARACTER: &'static [u8] = b"\xEF\xBF\xBD"; + +/// A Unicode code point: from U+0000 to U+10FFFF. +/// +/// Compare with the `char` type, +/// which represents a Unicode scalar value: +/// a code point that is not a surrogate (U+D800 to U+DFFF). +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy)] +pub struct CodePoint { + value: u32, +} + +/// Format the code point as `U+` followed by four to six hexadecimal digits. +/// Example: `U+1F4A9` +impl fmt::Debug for CodePoint { + #[inline] + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(formatter, "U+{:04X}", self.value) + } +} + +impl CodePoint { + /// Unsafely creates a new `CodePoint` without checking the value. + /// + /// Only use when `value` is known to be less than or equal to 0x10FFFF. + #[inline] + pub unsafe fn from_u32_unchecked(value: u32) -> CodePoint { + CodePoint { value: value } + } + + /// Creates a new `CodePoint` if the value is a valid code point. + /// + /// Returns `None` if `value` is above 0x10FFFF. + #[inline] + pub fn from_u32(value: u32) -> Option { + match value { + 0...0x10FFFF => Some(CodePoint { value: value }), + _ => None, + } + } + + /// Creates a new `CodePoint` from a `char`. + /// + /// Since all Unicode scalar values are code points, this always succeeds. + #[inline] + pub fn from_char(value: char) -> CodePoint { + CodePoint { value: value as u32 } + } + + /// Returns the numeric value of the code point. + #[inline] + pub fn to_u32(&self) -> u32 { + self.value + } + + /// Optionally returns a Unicode scalar value for the code point. + /// + /// Returns `None` if the code point is a surrogate (from U+D800 to U+DFFF). + #[inline] + pub fn to_char(&self) -> Option { + match self.value { + 0xD800...0xDFFF => None, + _ => Some(unsafe { char::from_u32_unchecked(self.value) }), + } + } + + /// Returns a Unicode scalar value for the code point. + /// + /// Returns `'\u{FFFD}'` (the replacement character “�”) + /// if the code point is a surrogate (from U+D800 to U+DFFF). + #[inline] + pub fn to_char_lossy(&self) -> char { + self.to_char().unwrap_or('\u{FFFD}') + } +} + +/// An owned, growable string of well-formed WTF-8 data. +/// +/// Similar to `String`, but can additionally contain surrogate code points +/// if they’re not in a surrogate pair. +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone)] +pub struct Wtf8Buf { + bytes: Vec, +} + +impl ops::Deref for Wtf8Buf { + type Target = Wtf8; + + fn deref(&self) -> &Wtf8 { + self.as_slice() + } +} + +/// Format the string with double quotes, +/// and surrogates as `\u` followed by four hexadecimal digits. +/// Example: `"a\u{D800}"` for a string with code points [U+0061, U+D800] +impl fmt::Debug for Wtf8Buf { + #[inline] + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&**self, formatter) + } +} + +impl Wtf8Buf { + /// Creates a new, empty WTF-8 string. + #[inline] + pub fn new() -> Wtf8Buf { + Wtf8Buf { bytes: Vec::new() } + } + + /// Creates a new, empty WTF-8 string with pre-allocated capacity for `n` bytes. + #[inline] + pub fn with_capacity(n: usize) -> Wtf8Buf { + Wtf8Buf { bytes: Vec::with_capacity(n) } + } + + /// Creates a WTF-8 string from a UTF-8 `String`. + /// + /// This takes ownership of the `String` and does not copy. + /// + /// Since WTF-8 is a superset of UTF-8, this always succeeds. + #[inline] + pub fn from_string(string: String) -> Wtf8Buf { + Wtf8Buf { bytes: string.into_bytes() } + } + + /// Creates a WTF-8 string from a UTF-8 `&str` slice. + /// + /// This copies the content of the slice. + /// + /// Since WTF-8 is a superset of UTF-8, this always succeeds. + #[inline] + pub fn from_str(str: &str) -> Wtf8Buf { + Wtf8Buf { bytes: <[_]>::to_vec(str.as_bytes()) } + } + + pub fn clear(&mut self) { + self.bytes.clear() + } + + /// Creates a WTF-8 string 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. + pub fn from_wide(v: &[u16]) -> Wtf8Buf { + let mut string = Wtf8Buf::with_capacity(v.len()); + for item in char::decode_utf16(v.iter().cloned()) { + match item { + Ok(ch) => string.push_char(ch), + Err(surrogate) => { + let surrogate = surrogate.unpaired_surrogate(); + // Surrogates are known to be in the code point range. + let code_point = unsafe { CodePoint::from_u32_unchecked(surrogate as u32) }; + // Skip the WTF-8 concatenation check, + // surrogate pairs are already decoded by decode_utf16 + string.push_code_point_unchecked(code_point) + } + } + } + string + } + + /// Copied from String::push + /// This does **not** include the WTF-8 concatenation check. + fn push_code_point_unchecked(&mut self, code_point: CodePoint) { + let bytes = unsafe { char::from_u32_unchecked(code_point.value).encode_utf8() }; + self.bytes.extend_from_slice(bytes.as_slice()); + } + + #[inline] + pub fn as_slice(&self) -> &Wtf8 { + unsafe { Wtf8::from_bytes_unchecked(&self.bytes) } + } + + /// Reserves capacity for at least `additional` more bytes to be inserted + /// in the given `Wtf8Buf`. + /// The collection may reserve more space to avoid frequent reallocations. + /// + /// # Panics + /// + /// Panics if the new capacity overflows `usize`. + #[inline] + pub fn reserve(&mut self, additional: usize) { + self.bytes.reserve(additional) + } + + #[inline] + pub fn reserve_exact(&mut self, additional: usize) { + self.bytes.reserve_exact(additional) + } + + /// Returns the number of bytes that this string buffer can hold without reallocating. + #[inline] + pub fn capacity(&self) -> usize { + self.bytes.capacity() + } + + /// Append a UTF-8 slice at the end of the string. + #[inline] + pub fn push_str(&mut self, other: &str) { + self.bytes.extend_from_slice(other.as_bytes()) + } + + /// Append a WTF-8 slice at the end of the string. + /// + /// This replaces newly paired surrogates at the boundary + /// with a supplementary code point, + /// like concatenating ill-formed UTF-16 strings effectively would. + #[inline] + pub fn push_wtf8(&mut self, other: &Wtf8) { + match ((&*self).final_lead_surrogate(), other.initial_trail_surrogate()) { + // Replace newly paired surrogates by a supplementary code point. + (Some(lead), Some(trail)) => { + let len_without_lead_surrogate = self.len() - 3; + self.bytes.truncate(len_without_lead_surrogate); + let other_without_trail_surrogate = &other.bytes[3..]; + // 4 bytes for the supplementary code point + self.bytes.reserve(4 + other_without_trail_surrogate.len()); + self.push_char(decode_surrogate_pair(lead, trail)); + self.bytes.extend_from_slice(other_without_trail_surrogate); + } + _ => self.bytes.extend_from_slice(&other.bytes), + } + } + + /// Append a Unicode scalar value at the end of the string. + #[inline] + pub fn push_char(&mut self, c: char) { + self.push_code_point_unchecked(CodePoint::from_char(c)) + } + + /// Append a code point at the end of the string. + /// + /// This replaces newly paired surrogates at the boundary + /// with a supplementary code point, + /// like concatenating ill-formed UTF-16 strings effectively would. + #[inline] + pub fn push(&mut self, code_point: CodePoint) { + if let trail @ 0xDC00...0xDFFF = code_point.to_u32() { + if let Some(lead) = (&*self).final_lead_surrogate() { + let len_without_lead_surrogate = self.len() - 3; + self.bytes.truncate(len_without_lead_surrogate); + self.push_char(decode_surrogate_pair(lead, trail as u16)); + return; + } + } + + // No newly paired surrogates at the boundary. + self.push_code_point_unchecked(code_point) + } + + /// Shortens a string to the specified length. + /// + /// # Panics + /// + /// Panics if `new_len` > current length, + /// or if `new_len` is not a code point boundary. + #[inline] + pub fn truncate(&mut self, new_len: usize) { + assert!(is_code_point_boundary(self, new_len)); + self.bytes.truncate(new_len) + } + + /// Consumes the WTF-8 string and tries to convert it to UTF-8. + /// + /// This does not copy the data. + /// + /// If the contents are not well-formed UTF-8 + /// (that is, if the string contains surrogates), + /// the original WTF-8 string is returned instead. + pub fn into_string(self) -> Result { + match self.next_surrogate(0) { + None => Ok(unsafe { String::from_utf8_unchecked(self.bytes) }), + Some(_) => Err(self), + } + } + + /// Consumes the WTF-8 string and converts it lossily to UTF-8. + /// + /// This does not copy the data (but may overwrite parts of it in place). + /// + /// Surrogates are replaced with `"\u{FFFD}"` (the replacement character “�”) + pub fn into_string_lossy(mut self) -> String { + let mut pos = 0; + loop { + match self.next_surrogate(pos) { + Some((surrogate_pos, _)) => { + pos = surrogate_pos + 3; + self.bytes[surrogate_pos..pos].copy_from_slice(UTF8_REPLACEMENT_CHARACTER); + } + None => return unsafe { String::from_utf8_unchecked(self.bytes) }, + } + } + } +} + +/// Create a new WTF-8 string from an iterator of code points. +/// +/// This replaces surrogate code point pairs with supplementary code points, +/// like concatenating ill-formed UTF-16 strings effectively would. +impl FromIterator for Wtf8Buf { + fn from_iter>(iter: T) -> Wtf8Buf { + let mut string = Wtf8Buf::new(); + string.extend(iter); + string + } +} + +/// Append code points from an iterator to the string. +/// +/// This replaces surrogate code point pairs with supplementary code points, +/// like concatenating ill-formed UTF-16 strings effectively would. +impl Extend for Wtf8Buf { + fn extend>(&mut self, iter: T) { + let iterator = iter.into_iter(); + let (low, _high) = iterator.size_hint(); + // Lower bound of one byte per code point (ASCII only) + self.bytes.reserve(low); + for code_point in iterator { + self.push(code_point); + } + } +} + +/// A borrowed slice of well-formed WTF-8 data. +/// +/// Similar to `&str`, but can additionally contain surrogate code points +/// if they’re not in a surrogate pair. +#[derive(Eq, Ord, PartialEq, PartialOrd)] +pub struct Wtf8 { + bytes: [u8], +} + +impl AsInner<[u8]> for Wtf8 { + fn as_inner(&self) -> &[u8] { + &self.bytes + } +} + +/// Format the slice with double quotes, +/// and surrogates as `\u` followed by four hexadecimal digits. +/// Example: `"a\u{D800}"` for a slice with code points [U+0061, U+D800] +impl fmt::Debug for Wtf8 { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn write_str_escaped(f: &mut fmt::Formatter, s: &str) -> fmt::Result { + use core::fmt::Write; + for c in s.chars().flat_map(|c| c.escape_debug()) { + f.write_char(c)? + } + Ok(()) + } + + formatter.write_str("\"")?; + let mut pos = 0; + loop { + match self.next_surrogate(pos) { + None => break, + Some((surrogate_pos, surrogate)) => { + write_str_escaped(formatter, unsafe { + str::from_utf8_unchecked(&self.bytes[pos..surrogate_pos]) + }) + ?; + write!(formatter, "\\u{{{:x}}}", surrogate)?; + pos = surrogate_pos + 3; + } + } + } + write_str_escaped(formatter, + unsafe { str::from_utf8_unchecked(&self.bytes[pos..]) }) + ?; + formatter.write_str("\"") + } +} + +impl Wtf8 { + /// Creates a WTF-8 slice from a UTF-8 `&str` slice. + /// + /// Since WTF-8 is a superset of UTF-8, this always succeeds. + #[inline] + pub fn from_str(value: &str) -> &Wtf8 { + unsafe { Wtf8::from_bytes_unchecked(value.as_bytes()) } + } + + /// Creates a WTF-8 slice from a WTF-8 byte slice. + /// + /// Since the byte slice is not checked for valid WTF-8, this functions is + /// marked unsafe. + #[inline] + unsafe fn from_bytes_unchecked(value: &[u8]) -> &Wtf8 { + mem::transmute(value) + } + + /// Returns the length, in WTF-8 bytes. + #[inline] + pub fn len(&self) -> usize { + self.bytes.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.bytes.is_empty() + } + + /// Returns the code point at `position` if it is in the ASCII range, + /// or `b'\xFF' otherwise. + /// + /// # Panics + /// + /// Panics if `position` is beyond the end of the string. + #[inline] + pub fn ascii_byte_at(&self, position: usize) -> u8 { + match self.bytes[position] { + ascii_byte @ 0x00...0x7F => ascii_byte, + _ => 0xFF, + } + } + + /// Returns an iterator for the string’s code points. + #[inline] + pub fn code_points(&self) -> Wtf8CodePoints { + Wtf8CodePoints { bytes: self.bytes.iter() } + } + + /// Tries to convert the string to UTF-8 and return a `&str` slice. + /// + /// Returns `None` if the string contains surrogates. + /// + /// This does not copy the data. + #[inline] + pub fn as_str(&self) -> Option<&str> { + // Well-formed WTF-8 is also well-formed UTF-8 + // if and only if it contains no surrogate. + match self.next_surrogate(0) { + None => Some(unsafe { str::from_utf8_unchecked(&self.bytes) }), + Some(_) => None, + } + } + + /// Lossily converts the string to UTF-8. + /// Returns a UTF-8 `&str` slice if the contents are well-formed in UTF-8. + /// + /// Surrogates are replaced with `"\u{FFFD}"` (the replacement character “�”). + /// + /// This only copies the data if necessary (if it contains any surrogate). + pub fn to_string_lossy(&self) -> Cow { + let surrogate_pos = match self.next_surrogate(0) { + None => return Cow::Borrowed(unsafe { str::from_utf8_unchecked(&self.bytes) }), + Some((pos, _)) => pos, + }; + let wtf8_bytes = &self.bytes; + let mut utf8_bytes = Vec::with_capacity(self.len()); + utf8_bytes.extend_from_slice(&wtf8_bytes[..surrogate_pos]); + utf8_bytes.extend_from_slice(UTF8_REPLACEMENT_CHARACTER); + let mut pos = surrogate_pos + 3; + loop { + match self.next_surrogate(pos) { + Some((surrogate_pos, _)) => { + utf8_bytes.extend_from_slice(&wtf8_bytes[pos..surrogate_pos]); + utf8_bytes.extend_from_slice(UTF8_REPLACEMENT_CHARACTER); + pos = surrogate_pos + 3; + } + None => { + utf8_bytes.extend_from_slice(&wtf8_bytes[pos..]); + return Cow::Owned(unsafe { String::from_utf8_unchecked(utf8_bytes) }); + } + } + } + } + + /// Converts the WTF-8 string to potentially ill-formed UTF-16 + /// and return an iterator of 16-bit code units. + /// + /// This is lossless: + /// calling `Wtf8Buf::from_ill_formed_utf16` on the resulting code units + /// would always return the original WTF-8 string. + #[inline] + pub fn encode_wide(&self) -> EncodeWide { + EncodeWide { + code_points: self.code_points(), + extra: 0, + } + } + + #[inline] + fn next_surrogate(&self, mut pos: usize) -> Option<(usize, u16)> { + let mut iter = self.bytes[pos..].iter(); + loop { + let b = match iter.next() { + None => return None, + Some(&b) => b, + }; + if b < 0x80 { + pos += 1; + } else if b < 0xE0 { + iter.next(); + pos += 2; + } else if b == 0xED { + match (iter.next(), iter.next()) { + (Some(&b2), Some(&b3)) if b2 >= 0xA0 => { + return Some((pos, decode_surrogate(b2, b3))) + } + _ => pos += 3, + } + } else if b < 0xF0 { + iter.next(); + iter.next(); + pos += 3; + } else { + iter.next(); + iter.next(); + iter.next(); + pos += 4; + } + } + } + + #[inline] + fn final_lead_surrogate(&self) -> Option { + let len = self.len(); + if len < 3 { + return None; + } + match &self.bytes[(len - 3)..] { + &[0xED, b2 @ 0xA0...0xAF, b3] => Some(decode_surrogate(b2, b3)), + _ => None, + } + } + + #[inline] + fn initial_trail_surrogate(&self) -> Option { + let len = self.len(); + if len < 3 { + return None; + } + match &self.bytes[..3] { + &[0xED, b2 @ 0xB0...0xBF, b3] => Some(decode_surrogate(b2, b3)), + _ => None, + } + } +} + + +/// Return a slice of the given string for the byte range [`begin`..`end`). +/// +/// # Panics +/// +/// Panics when `begin` and `end` do not point to code point boundaries, +/// or point beyond the end of the string. +impl ops::Index> for Wtf8 { + type Output = Wtf8; + + #[inline] + fn index(&self, range: ops::Range) -> &Wtf8 { + // is_code_point_boundary checks that the index is in [0, .len()] + if range.start <= range.end && is_code_point_boundary(self, range.start) && + is_code_point_boundary(self, range.end) { + unsafe { slice_unchecked(self, range.start, range.end) } + } else { + slice_error_fail(self, range.start, range.end) + } + } +} + +/// Return a slice of the given string from byte `begin` to its end. +/// +/// # Panics +/// +/// Panics when `begin` is not at a code point boundary, +/// or is beyond the end of the string. +impl ops::Index> for Wtf8 { + type Output = Wtf8; + + #[inline] + fn index(&self, range: ops::RangeFrom) -> &Wtf8 { + // is_code_point_boundary checks that the index is in [0, .len()] + if is_code_point_boundary(self, range.start) { + unsafe { slice_unchecked(self, range.start, self.len()) } + } else { + slice_error_fail(self, range.start, self.len()) + } + } +} + +/// Return a slice of the given string from its beginning to byte `end`. +/// +/// # Panics +/// +/// Panics when `end` is not at a code point boundary, +/// or is beyond the end of the string. +impl ops::Index> for Wtf8 { + type Output = Wtf8; + + #[inline] + fn index(&self, range: ops::RangeTo) -> &Wtf8 { + // is_code_point_boundary checks that the index is in [0, .len()] + if is_code_point_boundary(self, range.end) { + unsafe { slice_unchecked(self, 0, range.end) } + } else { + slice_error_fail(self, 0, range.end) + } + } +} + +impl ops::Index for Wtf8 { + type Output = Wtf8; + + #[inline] + fn index(&self, _range: ops::RangeFull) -> &Wtf8 { + self + } +} + +#[inline] +fn decode_surrogate(second_byte: u8, third_byte: u8) -> u16 { + // The first byte is assumed to be 0xED + 0xD800 | (second_byte as u16 & 0x3F) << 6 | third_byte as u16 & 0x3F +} + +#[inline] +fn decode_surrogate_pair(lead: u16, trail: u16) -> char { + let code_point = 0x10000 + ((((lead - 0xD800) as u32) << 10) | (trail - 0xDC00) as u32); + unsafe { char::from_u32_unchecked(code_point) } +} + +/// Copied from core::str::StrPrelude::is_char_boundary +#[inline] +pub fn is_code_point_boundary(slice: &Wtf8, index: usize) -> bool { + if index == slice.len() { + return true; + } + match slice.bytes.get(index) { + None => false, + Some(&b) => b < 128 || b >= 192, + } +} + +/// Copied from core::str::raw::slice_unchecked +#[inline] +pub unsafe fn slice_unchecked(s: &Wtf8, begin: usize, end: usize) -> &Wtf8 { + // memory layout of an &[u8] and &Wtf8 are the same + Wtf8::from_bytes_unchecked(slice::from_raw_parts(s.bytes.as_ptr().offset(begin as isize), + end - begin)) +} + +/// Copied from core::str::raw::slice_error_fail +#[inline(never)] +pub fn slice_error_fail(s: &Wtf8, begin: usize, end: usize) -> ! { + assert!(begin <= end); + panic!("index {} and/or {} in `{:?}` do not lie on character boundary", + begin, + end, + s); +} + +/// Iterator for the code points of a WTF-8 string. +/// +/// Created with the method `.code_points()`. +#[derive(Clone)] +pub struct Wtf8CodePoints<'a> { + bytes: slice::Iter<'a, u8>, +} + +impl<'a> Iterator for Wtf8CodePoints<'a> { + type Item = CodePoint; + + #[inline] + fn next(&mut self) -> Option { + next_code_point(&mut self.bytes).map(|c| CodePoint { value: c }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let len = self.bytes.len(); + (len.saturating_add(3) / 4, Some(len)) + } +} + +#[derive(Clone)] +pub struct EncodeWide<'a> { + code_points: Wtf8CodePoints<'a>, + extra: u16, +} + +// Copied from libunicode/u_str.rs +impl<'a> Iterator for EncodeWide<'a> { + type Item = u16; + + #[inline] + fn next(&mut self) -> Option { + if self.extra != 0 { + let tmp = self.extra; + self.extra = 0; + return Some(tmp); + } + + self.code_points.next().map(|code_point| { + let n = unsafe { char::from_u32_unchecked(code_point.value).encode_utf16() }; + let n = n.as_slice(); + if n.len() == 2 { + self.extra = n[1]; + } + n[0] + }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let (low, high) = self.code_points.size_hint(); + // every code point gets either one u16 or two u16, + // so this iterator is between 1 or 2 times as + // long as the underlying iterator. + (low, high.and_then(|n| n.checked_mul(2))) + } +} + +impl Hash for CodePoint { + #[inline] + fn hash(&self, state: &mut H) { + self.value.hash(state) + } +} + +impl Hash for Wtf8Buf { + #[inline] + fn hash(&self, state: &mut H) { + state.write(&self.bytes); + 0xfeu8.hash(state) + } +} + +impl Hash for Wtf8 { + #[inline] + fn hash(&self, state: &mut H) { + state.write(&self.bytes); + 0xfeu8.hash(state) + } +} + +impl AsciiExt for Wtf8 { + type Owned = Wtf8Buf; + + fn is_ascii(&self) -> bool { + self.bytes.is_ascii() + } + fn to_ascii_uppercase(&self) -> Wtf8Buf { + Wtf8Buf { bytes: self.bytes.to_ascii_uppercase() } + } + fn to_ascii_lowercase(&self) -> Wtf8Buf { + Wtf8Buf { bytes: self.bytes.to_ascii_lowercase() } + } + fn eq_ignore_ascii_case(&self, other: &Wtf8) -> bool { + self.bytes.eq_ignore_ascii_case(&other.bytes) + } + + fn make_ascii_uppercase(&mut self) { + self.bytes.make_ascii_uppercase() + } + fn make_ascii_lowercase(&mut self) { + self.bytes.make_ascii_lowercase() + } +} + +#[cfg(test)] +mod tests { + use collections::borrow::Cow; + use collections::{String, Vec}; + use super::*; + + #[test] + fn code_point_from_u32() { + assert!(CodePoint::from_u32(0).is_some()); + assert!(CodePoint::from_u32(0xD800).is_some()); + assert!(CodePoint::from_u32(0x10FFFF).is_some()); + assert!(CodePoint::from_u32(0x110000).is_none()); + } + + #[test] + fn code_point_to_u32() { + fn c(value: u32) -> CodePoint { + CodePoint::from_u32(value).unwrap() + } + assert_eq!(c(0).to_u32(), 0); + assert_eq!(c(0xD800).to_u32(), 0xD800); + assert_eq!(c(0x10FFFF).to_u32(), 0x10FFFF); + } + + #[test] + fn code_point_from_char() { + assert_eq!(CodePoint::from_char('a').to_u32(), 0x61); + assert_eq!(CodePoint::from_char('💩').to_u32(), 0x1F4A9); + } + + #[test] + fn code_point_to_string() { + assert_eq!(format!("{:?}", CodePoint::from_char('a')), "U+0061"); + assert_eq!(format!("{:?}", CodePoint::from_char('💩')), "U+1F4A9"); + } + + #[test] + fn code_point_to_char() { + fn c(value: u32) -> CodePoint { + CodePoint::from_u32(value).unwrap() + } + assert_eq!(c(0x61).to_char(), Some('a')); + assert_eq!(c(0x1F4A9).to_char(), Some('💩')); + assert_eq!(c(0xD800).to_char(), None); + } + + #[test] + fn code_point_to_char_lossy() { + fn c(value: u32) -> CodePoint { + CodePoint::from_u32(value).unwrap() + } + assert_eq!(c(0x61).to_char_lossy(), 'a'); + assert_eq!(c(0x1F4A9).to_char_lossy(), '💩'); + assert_eq!(c(0xD800).to_char_lossy(), '\u{FFFD}'); + } + + #[test] + fn wtf8buf_new() { + assert_eq!(Wtf8Buf::new().bytes, b""); + } + + #[test] + fn wtf8buf_from_str() { + assert_eq!(Wtf8Buf::from_str("").bytes, b""); + assert_eq!(Wtf8Buf::from_str("aé 💩").bytes, + b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + } + + #[test] + fn wtf8buf_from_string() { + assert_eq!(Wtf8Buf::from_string(String::from("")).bytes, b""); + assert_eq!(Wtf8Buf::from_string(String::from("aé 💩")).bytes, + b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + } + + #[test] + fn wtf8buf_from_wide() { + assert_eq!(Wtf8Buf::from_wide(&[]).bytes, b""); + assert_eq!(Wtf8Buf::from_wide(&[0x61, 0xE9, 0x20, 0xD83D, 0xD83D, 0xDCA9]).bytes, + b"a\xC3\xA9 \xED\xA0\xBD\xF0\x9F\x92\xA9"); + } + + #[test] + fn wtf8buf_push_str() { + let mut string = Wtf8Buf::new(); + assert_eq!(string.bytes, b""); + string.push_str("aé 💩"); + assert_eq!(string.bytes, b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + } + + #[test] + fn wtf8buf_push_char() { + let mut string = Wtf8Buf::from_str("aé "); + assert_eq!(string.bytes, b"a\xC3\xA9 "); + string.push_char('💩'); + assert_eq!(string.bytes, b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + } + + #[test] + fn wtf8buf_push() { + let mut string = Wtf8Buf::from_str("aé "); + assert_eq!(string.bytes, b"a\xC3\xA9 "); + string.push(CodePoint::from_char('💩')); + assert_eq!(string.bytes, b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + + fn c(value: u32) -> CodePoint { + CodePoint::from_u32(value).unwrap() + } + + let mut string = Wtf8Buf::new(); + string.push(c(0xD83D)); // lead + string.push(c(0xDCA9)); // trail + assert_eq!(string.bytes, b"\xF0\x9F\x92\xA9"); // Magic! + + let mut string = Wtf8Buf::new(); + string.push(c(0xD83D)); // lead + string.push(c(0x20)); // not surrogate + string.push(c(0xDCA9)); // trail + assert_eq!(string.bytes, b"\xED\xA0\xBD \xED\xB2\xA9"); + + let mut string = Wtf8Buf::new(); + string.push(c(0xD800)); // lead + string.push(c(0xDBFF)); // lead + assert_eq!(string.bytes, b"\xED\xA0\x80\xED\xAF\xBF"); + + let mut string = Wtf8Buf::new(); + string.push(c(0xD800)); // lead + string.push(c(0xE000)); // not surrogate + assert_eq!(string.bytes, b"\xED\xA0\x80\xEE\x80\x80"); + + let mut string = Wtf8Buf::new(); + string.push(c(0xD7FF)); // not surrogate + string.push(c(0xDC00)); // trail + assert_eq!(string.bytes, b"\xED\x9F\xBF\xED\xB0\x80"); + + let mut string = Wtf8Buf::new(); + string.push(c(0x61)); // not surrogate, < 3 bytes + string.push(c(0xDC00)); // trail + assert_eq!(string.bytes, b"\x61\xED\xB0\x80"); + + let mut string = Wtf8Buf::new(); + string.push(c(0xDC00)); // trail + assert_eq!(string.bytes, b"\xED\xB0\x80"); + } + + #[test] + fn wtf8buf_push_wtf8() { + let mut string = Wtf8Buf::from_str("aé"); + assert_eq!(string.bytes, b"a\xC3\xA9"); + string.push_wtf8(Wtf8::from_str(" 💩")); + assert_eq!(string.bytes, b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + + fn w(v: &[u8]) -> &Wtf8 { + unsafe { Wtf8::from_bytes_unchecked(v) } + } + + let mut string = Wtf8Buf::new(); + string.push_wtf8(w(b"\xED\xA0\xBD")); // lead + string.push_wtf8(w(b"\xED\xB2\xA9")); // trail + assert_eq!(string.bytes, b"\xF0\x9F\x92\xA9"); // Magic! + + let mut string = Wtf8Buf::new(); + string.push_wtf8(w(b"\xED\xA0\xBD")); // lead + string.push_wtf8(w(b" ")); // not surrogate + string.push_wtf8(w(b"\xED\xB2\xA9")); // trail + assert_eq!(string.bytes, b"\xED\xA0\xBD \xED\xB2\xA9"); + + let mut string = Wtf8Buf::new(); + string.push_wtf8(w(b"\xED\xA0\x80")); // lead + string.push_wtf8(w(b"\xED\xAF\xBF")); // lead + assert_eq!(string.bytes, b"\xED\xA0\x80\xED\xAF\xBF"); + + let mut string = Wtf8Buf::new(); + string.push_wtf8(w(b"\xED\xA0\x80")); // lead + string.push_wtf8(w(b"\xEE\x80\x80")); // not surrogate + assert_eq!(string.bytes, b"\xED\xA0\x80\xEE\x80\x80"); + + let mut string = Wtf8Buf::new(); + string.push_wtf8(w(b"\xED\x9F\xBF")); // not surrogate + string.push_wtf8(w(b"\xED\xB0\x80")); // trail + assert_eq!(string.bytes, b"\xED\x9F\xBF\xED\xB0\x80"); + + let mut string = Wtf8Buf::new(); + string.push_wtf8(w(b"a")); // not surrogate, < 3 bytes + string.push_wtf8(w(b"\xED\xB0\x80")); // trail + assert_eq!(string.bytes, b"\x61\xED\xB0\x80"); + + let mut string = Wtf8Buf::new(); + string.push_wtf8(w(b"\xED\xB0\x80")); // trail + assert_eq!(string.bytes, b"\xED\xB0\x80"); + } + + #[test] + fn wtf8buf_truncate() { + let mut string = Wtf8Buf::from_str("aé"); + string.truncate(1); + assert_eq!(string.bytes, b"a"); + } + + #[test] + #[should_panic] + fn wtf8buf_truncate_fail_code_point_boundary() { + let mut string = Wtf8Buf::from_str("aé"); + string.truncate(2); + } + + #[test] + #[should_panic] + fn wtf8buf_truncate_fail_longer() { + let mut string = Wtf8Buf::from_str("aé"); + string.truncate(4); + } + + #[test] + fn wtf8buf_into_string() { + let mut string = Wtf8Buf::from_str("aé 💩"); + assert_eq!(string.clone().into_string(), Ok(String::from("aé 💩"))); + string.push(CodePoint::from_u32(0xD800).unwrap()); + assert_eq!(string.clone().into_string(), Err(string)); + } + + #[test] + fn wtf8buf_into_string_lossy() { + let mut string = Wtf8Buf::from_str("aé 💩"); + assert_eq!(string.clone().into_string_lossy(), String::from("aé 💩")); + string.push(CodePoint::from_u32(0xD800).unwrap()); + assert_eq!(string.clone().into_string_lossy(), + String::from("aé 💩�")); + } + + #[test] + fn wtf8buf_from_iterator() { + fn f(values: &[u32]) -> Wtf8Buf { + values.iter().map(|&c| CodePoint::from_u32(c).unwrap()).collect::() + } + assert_eq!(f(&[0x61, 0xE9, 0x20, 0x1F4A9]).bytes, + b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + + assert_eq!(f(&[0xD83D, 0xDCA9]).bytes, b"\xF0\x9F\x92\xA9"); // Magic! + assert_eq!(f(&[0xD83D, 0x20, 0xDCA9]).bytes, + b"\xED\xA0\xBD \xED\xB2\xA9"); + assert_eq!(f(&[0xD800, 0xDBFF]).bytes, b"\xED\xA0\x80\xED\xAF\xBF"); + assert_eq!(f(&[0xD800, 0xE000]).bytes, b"\xED\xA0\x80\xEE\x80\x80"); + assert_eq!(f(&[0xD7FF, 0xDC00]).bytes, b"\xED\x9F\xBF\xED\xB0\x80"); + assert_eq!(f(&[0x61, 0xDC00]).bytes, b"\x61\xED\xB0\x80"); + assert_eq!(f(&[0xDC00]).bytes, b"\xED\xB0\x80"); + } + + #[test] + fn wtf8buf_extend() { + fn e(initial: &[u32], extended: &[u32]) -> Wtf8Buf { + fn c(value: &u32) -> CodePoint { + CodePoint::from_u32(*value).unwrap() + } + let mut string = initial.iter().map(c).collect::(); + string.extend(extended.iter().map(c)); + string + } + + assert_eq!(e(&[0x61, 0xE9], &[0x20, 0x1F4A9]).bytes, + b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + + assert_eq!(e(&[0xD83D], &[0xDCA9]).bytes, b"\xF0\x9F\x92\xA9"); // Magic! + assert_eq!(e(&[0xD83D, 0x20], &[0xDCA9]).bytes, + b"\xED\xA0\xBD \xED\xB2\xA9"); + assert_eq!(e(&[0xD800], &[0xDBFF]).bytes, b"\xED\xA0\x80\xED\xAF\xBF"); + assert_eq!(e(&[0xD800], &[0xE000]).bytes, b"\xED\xA0\x80\xEE\x80\x80"); + assert_eq!(e(&[0xD7FF], &[0xDC00]).bytes, b"\xED\x9F\xBF\xED\xB0\x80"); + assert_eq!(e(&[0x61], &[0xDC00]).bytes, b"\x61\xED\xB0\x80"); + assert_eq!(e(&[], &[0xDC00]).bytes, b"\xED\xB0\x80"); + } + + #[test] + fn wtf8buf_show() { + let mut string = Wtf8Buf::from_str("a\té \u{7f}💩\r"); + string.push(CodePoint::from_u32(0xD800).unwrap()); + assert_eq!(format!("{:?}", string), + "\"a\\té \\u{7f}\u{1f4a9}\\r\\u{d800}\""); + } + + #[test] + fn wtf8buf_as_slice() { + assert_eq!(Wtf8Buf::from_str("aé").as_slice(), Wtf8::from_str("aé")); + } + + #[test] + fn wtf8buf_show_str() { + let text = "a\té 💩\r"; + let string = Wtf8Buf::from_str(text); + assert_eq!(format!("{:?}", text), format!("{:?}", string)); + } + + #[test] + fn wtf8_from_str() { + assert_eq!(&Wtf8::from_str("").bytes, b""); + assert_eq!(&Wtf8::from_str("aé 💩").bytes, + b"a\xC3\xA9 \xF0\x9F\x92\xA9"); + } + + #[test] + fn wtf8_len() { + assert_eq!(Wtf8::from_str("").len(), 0); + assert_eq!(Wtf8::from_str("aé 💩").len(), 8); + } + + #[test] + fn wtf8_slice() { + assert_eq!(&Wtf8::from_str("aé 💩")[1..4].bytes, b"\xC3\xA9 "); + } + + #[test] + #[should_panic] + fn wtf8_slice_not_code_point_boundary() { + &Wtf8::from_str("aé 💩")[2..4]; + } + + #[test] + fn wtf8_slice_from() { + assert_eq!(&Wtf8::from_str("aé 💩")[1..].bytes, + b"\xC3\xA9 \xF0\x9F\x92\xA9"); + } + + #[test] + #[should_panic] + fn wtf8_slice_from_not_code_point_boundary() { + &Wtf8::from_str("aé 💩")[2..]; + } + + #[test] + fn wtf8_slice_to() { + assert_eq!(&Wtf8::from_str("aé 💩")[..4].bytes, b"a\xC3\xA9 "); + } + + #[test] + #[should_panic] + fn wtf8_slice_to_not_code_point_boundary() { + &Wtf8::from_str("aé 💩")[5..]; + } + + #[test] + fn wtf8_ascii_byte_at() { + let slice = Wtf8::from_str("aé 💩"); + assert_eq!(slice.ascii_byte_at(0), b'a'); + assert_eq!(slice.ascii_byte_at(1), b'\xFF'); + assert_eq!(slice.ascii_byte_at(2), b'\xFF'); + assert_eq!(slice.ascii_byte_at(3), b' '); + assert_eq!(slice.ascii_byte_at(4), b'\xFF'); + } + + #[test] + fn wtf8_code_points() { + fn c(value: u32) -> CodePoint { + CodePoint::from_u32(value).unwrap() + } + fn cp(string: &Wtf8Buf) -> Vec> { + string.code_points().map(|c| c.to_char()).collect::>() + } + let mut string = Wtf8Buf::from_str("é "); + assert_eq!(cp(&string), [Some('é'), Some(' ')]); + string.push(c(0xD83D)); + assert_eq!(cp(&string), [Some('é'), Some(' '), None]); + string.push(c(0xDCA9)); + assert_eq!(cp(&string), [Some('é'), Some(' '), Some('💩')]); + } + + #[test] + fn wtf8_as_str() { + assert_eq!(Wtf8::from_str("").as_str(), Some("")); + assert_eq!(Wtf8::from_str("aé 💩").as_str(), Some("aé 💩")); + let mut string = Wtf8Buf::new(); + string.push(CodePoint::from_u32(0xD800).unwrap()); + assert_eq!(string.as_str(), None); + } + + #[test] + fn wtf8_to_string_lossy() { + assert_eq!(Wtf8::from_str("").to_string_lossy(), Cow::Borrowed("")); + assert_eq!(Wtf8::from_str("aé 💩").to_string_lossy(), + Cow::Borrowed("aé 💩")); + let mut string = Wtf8Buf::from_str("aé 💩"); + string.push(CodePoint::from_u32(0xD800).unwrap()); + let expected: Cow = Cow::Owned(String::from("aé 💩�")); + assert_eq!(string.to_string_lossy(), expected); + } + + #[test] + fn wtf8_encode_wide() { + let mut string = Wtf8Buf::from_str("aé "); + string.push(CodePoint::from_u32(0xD83D).unwrap()); + string.push_char('💩'); + assert_eq!(string.encode_wide().collect::>(), + vec![0x61, 0xE9, 0x20, 0xD83D, 0xD83D, 0xDCA9]); + } +} From b8a08d5b088866b893e3b2d1e8c99e67bc38d8f2 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 12 Aug 2016 16:01:17 -0700 Subject: [PATCH 02/28] Regenerate raw fs bindings --- ctru-sys/src/services/fs.rs | 401 ++++++++++++++++++------------------ 1 file changed, 200 insertions(+), 201 deletions(-) diff --git a/ctru-sys/src/services/fs.rs b/ctru-sys/src/services/fs.rs index 0608bb9..421bbcb 100644 --- a/ctru-sys/src/services/fs.rs +++ b/ctru-sys/src/services/fs.rs @@ -1,44 +1,45 @@ -// TODO: Determine if anonymous enums are properly represented (they probably aren't) +/* automatically generated by rust-bindgen */ + +#![allow(dead_code, + non_camel_case_types, + non_upper_case_globals, + non_snake_case)] use ::{Handle, Result}; -use ::libc::c_void; -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed1 { - FS_OPEN_READ = 1, - FS_OPEN_WRITE = 2, - FS_OPEN_CREATE = 4, -} +pub const FS_OPEN_READ: u32 = 1; +pub const FS_OPEN_WRITE: u32 = 2; +pub const FS_OPEN_CREATE: u32 = 4; -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed2 { - FS_WRITE_FLUSH = 1, - FS_WRITE_UPDATE_TIME = 256, -} +pub const FS_WRITE_FLUSH: u32 = 1; +pub const FS_WRITE_UPDATE_TIME: u32 = 256; -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed3 { - FS_ATTRIBUTE_DIRECTORY = 1, - FS_ATTRIBUTE_HIDDEN = 256, - FS_ATTRIBUTE_ARCHIVE = 65536, - FS_ATTRIBUTE_READ_ONLY = 16777216, -} +pub const FS_ATTRIBUTE_DIRECTORY: u32 = 1; +pub const FS_ATTRIBUTE_HIDDEN: u32 = 256; +pub const FS_ATTRIBUTE_ARCHIVE: u32 = 65536; +pub const FS_ATTRIBUTE_READ_ONLY: u32 = 16777216; -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed4 { +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_MediaType { MEDIATYPE_NAND = 0, MEDIATYPE_SD = 1, MEDIATYPE_GAME_CARD = 2, } - -pub type FS_MediaType = Enum_Unnamed4; -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed5 { +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_SystemMediaType { + SYSTEM_MEDIATYPE_CTR_NAND = 0, + SYSTEM_MEDIATYPE_TWL_NAND = 1, + SYSTEM_MEDIATYPE_SD = 2, + SYSTEM_MEDIATYPE_TWL_PHOTO = 3, +} +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_ArchiveID { ARCHIVE_ROMFS = 3, ARCHIVE_SAVEDATA = 4, ARCHIVE_EXTDATA = 6, @@ -57,33 +58,31 @@ pub enum Enum_Unnamed5 { ARCHIVE_SAVEDATA_AND_CONTENT2 = 591751054, ARCHIVE_NAND_CTR_FS = 1450741931, ARCHIVE_TWL_PHOTO = 1450741932, + ARCHIVE_TWL_SOUND = 1450741933, ARCHIVE_NAND_TWL_FS = 1450741934, ARCHIVE_NAND_W_FS = 1450741935, ARCHIVE_GAMECARD_SAVEDATA = 1450741937, ARCHIVE_USER_SAVEDATA = 1450741938, ARCHIVE_DEMO_SAVEDATA = 1450741940, } -pub type FS_ArchiveID = Enum_Unnamed5; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed6 { +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_PathType { PATH_INVALID = 0, PATH_EMPTY = 1, PATH_BINARY = 2, PATH_ASCII = 3, PATH_UTF16 = 4, } -pub type FS_PathType = Enum_Unnamed6; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed7 { SECUREVALUE_SLOT_SD = 4096, } -pub type FS_SecureValueSlot = Enum_Unnamed7; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed8 { +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_SecureValueSlot { SECUREVALUE_SLOT_SD = 4096, } +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_CardSpiBaudRate { BAUDRATE_512KHZ = 0, BAUDRATE_1MHZ = 1, BAUDRATE_2MHZ = 2, @@ -91,64 +90,51 @@ pub enum Enum_Unnamed8 { BAUDRATE_8MHZ = 4, BAUDRATE_16MHZ = 5, } -pub type FS_CardSpiBaudRate = Enum_Unnamed8; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed9 { - BUSMODE_1BIT = 0, - BUSMODE_4BIT = 1, -} -pub type FS_CardSpiBusMode = Enum_Unnamed9; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed10 { +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_CardSpiBusMode { BUSMODE_1BIT = 0, BUSMODE_4BIT = 1, } +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_SpecialContentType { SPECIALCONTENT_UPDATE = 1, SPECIALCONTENT_MANUAL = 2, SPECIALCONTENT_DLP_CHILD = 3, } -pub type FS_SpecialContentType = Enum_Unnamed10; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed11 { CARD_CTR = 0, CARD_TWL = 1, } -pub type FS_CardType = Enum_Unnamed11; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed12 { FS_ACTION_UNKNOWN = 0, } -pub type FS_Action = Enum_Unnamed12; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed13 { +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_CardType { CARD_CTR = 0, CARD_TWL = 1, } +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_Action { FS_ACTION_UNKNOWN = 0, } +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_ArchiveAction { ARCHIVE_ACTION_COMMIT_SAVE_DATA = 0, ARCHIVE_ACTION_GET_TIMESTAMP = 1, } -pub type FS_ArchiveAction = Enum_Unnamed13; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed14 { +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_SecureSaveAction { SECURESAVE_ACTION_DELETE = 0, SECURESAVE_ACTION_FORMAT = 1, } -pub type FS_SecureSaveAction = Enum_Unnamed14; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed15 { FILE_ACTION_UNKNOWN = 0, } -pub type FS_FileAction = Enum_Unnamed15; - -#[derive(Clone, Copy)] -#[repr(C)] -pub enum Enum_Unnamed16 { DIRECTORY_ACTION_UNKNOWN = 0, } -pub type FS_DirectoryAction = Enum_Unnamed16; - +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_FileAction { FILE_ACTION_UNKNOWN = 0, } +#[derive(Copy, Clone)] +#[repr(u32)] +#[derive(Debug)] +pub enum FS_DirectoryAction { DIRECTORY_ACTION_UNKNOWN = 0, } #[repr(C)] #[derive(Copy)] -pub struct Struct_Unnamed17 { +pub struct FS_DirectoryEntry { pub name: [u16; 262usize], pub shortName: [u8; 10usize], pub shortExt: [u8; 4usize], @@ -157,169 +143,175 @@ pub struct Struct_Unnamed17 { pub attributes: u32, pub fileSize: u64, } -impl ::core::clone::Clone for Struct_Unnamed17 { +impl ::core::clone::Clone for FS_DirectoryEntry { fn clone(&self) -> Self { *self } } -impl ::core::default::Default for Struct_Unnamed17 { +impl ::core::default::Default for FS_DirectoryEntry { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_DirectoryEntry = Struct_Unnamed17; - #[repr(C)] -#[derive(Copy)] -pub struct Struct_Unnamed18 { +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct FS_ArchiveResource { pub sectorSize: u32, pub clusterSize: u32, pub totalClusters: u32, pub freeClusters: u32, } -impl ::core::clone::Clone for Struct_Unnamed18 { - fn clone(&self) -> Self { *self } -} -impl ::core::default::Default for Struct_Unnamed18 { +impl ::core::default::Default for FS_ArchiveResource { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_ArchiveResource = Struct_Unnamed18; - #[repr(C)] -#[derive(Copy)] -pub struct Struct_Unnamed19 { +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct FS_ProgramInfo { pub programId: u64, pub _bindgen_bitfield_1_: FS_MediaType, pub padding: [u8; 7usize], } -impl ::core::clone::Clone for Struct_Unnamed19 { - fn clone(&self) -> Self { *self } -} -impl ::core::default::Default for Struct_Unnamed19 { +impl ::core::default::Default for FS_ProgramInfo { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_ProgramInfo = Struct_Unnamed19; - #[repr(C)] -#[derive(Copy)] -pub struct Struct_Unnamed20 { +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct FS_ProductInfo { pub productCode: [u8; 16usize], pub companyCode: [u8; 2usize], pub remasterVersion: u16, } -impl ::core::clone::Clone for Struct_Unnamed20 { - fn clone(&self) -> Self { *self } -} -impl ::core::default::Default for Struct_Unnamed20 { +impl ::core::default::Default for FS_ProductInfo { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_ProductInfo = Struct_Unnamed20; - #[repr(C)] #[derive(Copy)] -pub struct Struct_Unnamed21 { +pub struct FS_IntegrityVerificationSeed { pub aesCbcMac: [u8; 16usize], pub movableSed: [u8; 288usize], } -impl ::core::clone::Clone for Struct_Unnamed21 { +impl ::core::clone::Clone for FS_IntegrityVerificationSeed { fn clone(&self) -> Self { *self } } -impl ::core::default::Default for Struct_Unnamed21 { +impl ::core::default::Default for FS_IntegrityVerificationSeed { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_IntegrityVerificationSeed = Struct_Unnamed21; - #[repr(C, packed)] -#[derive(Copy)] -pub struct Struct_Unnamed22 { +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct FS_ExtSaveDataInfo { pub _bindgen_bitfield_1_: FS_MediaType, pub unknown: u8, pub reserved1: u16, pub saveId: u64, pub reserved2: u32, } -impl ::core::clone::Clone for Struct_Unnamed22 { - fn clone(&self) -> Self { *self } -} -impl ::core::default::Default for Struct_Unnamed22 { +impl ::core::default::Default for FS_ExtSaveDataInfo { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_ExtSaveDataInfo = Struct_Unnamed22; - #[repr(C)] -#[derive(Copy)] -pub struct Struct_Unnamed23 { +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct FS_SystemSaveDataInfo { pub _bindgen_bitfield_1_: FS_MediaType, pub unknown: u8, pub reserved: u16, pub saveId: u32, } -impl ::core::clone::Clone for Struct_Unnamed23 { - fn clone(&self) -> Self { *self } -} -impl ::core::default::Default for Struct_Unnamed23 { +impl ::core::default::Default for FS_SystemSaveDataInfo { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_SystemSaveDataInfo = Struct_Unnamed23; - #[repr(C)] -#[derive(Copy)] -pub struct Struct_Unnamed24 { +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct FS_DeviceMoveContext { pub ivs: [u8; 16usize], pub encryptParameter: [u8; 16usize], } -impl ::core::clone::Clone for Struct_Unnamed24 { - fn clone(&self) -> Self { *self } -} -impl ::core::default::Default for Struct_Unnamed24 { +impl ::core::default::Default for FS_DeviceMoveContext { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_DeviceMoveContext = Struct_Unnamed24; - #[repr(C)] -#[derive(Copy)] -pub struct Struct_Unnamed25 { - pub _type: FS_PathType, +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct FS_Path { + pub type_: FS_PathType, pub size: u32, - pub data: *const c_void, + pub data: *const ::libc::c_void, } -impl ::core::clone::Clone for Struct_Unnamed25 { - fn clone(&self) -> Self { *self } -} -impl ::core::default::Default for Struct_Unnamed25 { +impl ::core::default::Default for FS_Path { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_Path = Struct_Unnamed25; - +pub type FS_Archive = u64; #[repr(C)] -#[derive(Copy)] -pub struct Struct_Unnamed26 { - pub id: u32, - pub lowPath: FS_Path, - pub handle: u64, +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct romfs_header { + pub headerSize: u32, + pub dirHashTableOff: u32, + pub dirHashTableSize: u32, + pub dirTableOff: u32, + pub dirTableSize: u32, + pub fileHashTableOff: u32, + pub fileHashTableSize: u32, + pub fileTableOff: u32, + pub fileTableSize: u32, + pub fileDataOff: u32, +} +impl ::core::default::Default for romfs_header { + fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -impl ::core::clone::Clone for Struct_Unnamed26 { - fn clone(&self) -> Self { *self } +#[repr(C)] +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct romfs_dir { + pub parent: u32, + pub sibling: u32, + pub childDir: u32, + pub childFile: u32, + pub nextHash: u32, + pub nameLen: u32, + pub name: [u16; 0usize], +} +impl ::core::default::Default for romfs_dir { + fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -impl ::core::default::Default for Struct_Unnamed26 { +#[repr(C)] +#[derive(Copy, Clone)] +#[derive(Debug)] +pub struct romfs_file { + pub parent: u32, + pub sibling: u32, + pub dataOff: u64, + pub dataSize: u64, + pub nextHash: u32, + pub nameLen: u32, + pub name: [u16; 0usize], +} +impl ::core::default::Default for romfs_file { fn default() -> Self { unsafe { ::core::mem::zeroed() } } } -pub type FS_Archive = Struct_Unnamed26; +pub enum romfs_mount { } extern "C" { pub fn fsInit() -> Result; pub fn fsExit(); - pub fn fsUseSession(session: Handle, sdmc: u8); + pub fn fsUseSession(session: Handle); pub fn fsEndUseSession(); - pub fn fsMakePath(_type: FS_PathType, path: *const c_void) + pub fn fsExemptFromSession(archive: FS_Archive); + pub fn fsUnexemptFromSession(archive: FS_Archive); + pub fn fsMakePath(type_: FS_PathType, path: *const ::libc::c_void) -> FS_Path; pub fn fsGetSessionHandle() -> *mut Handle; - pub fn FSUSER_Control(action: FS_Action, - input: *mut c_void, inputSize: u32, - output: *mut c_void, + pub fn FSUSER_Control(action: FS_Action, input: *mut ::libc::c_void, + inputSize: u32, output: *mut ::libc::c_void, outputSize: u32) -> Result; pub fn FSUSER_Initialize(session: Handle) -> Result; pub fn FSUSER_OpenFile(out: *mut Handle, archive: FS_Archive, path: FS_Path, openFlags: u32, attributes: u32) -> Result; - pub fn FSUSER_OpenFileDirectly(out: *mut Handle, archive: FS_Archive, - path: FS_Path, openFlags: u32, - attributes: u32) -> Result; + pub fn FSUSER_OpenFileDirectly(out: *mut Handle, archiveId: FS_ArchiveID, + archivePath: FS_Path, filePath: FS_Path, + openFlags: u32, attributes: u32) + -> Result; pub fn FSUSER_DeleteFile(archive: FS_Archive, path: FS_Path) -> Result; pub fn FSUSER_RenameFile(srcArchive: FS_Archive, srcPath: FS_Path, dstArchive: FS_Archive, dstPath: FS_Path) @@ -337,17 +329,17 @@ extern "C" { -> Result; pub fn FSUSER_OpenDirectory(out: *mut Handle, archive: FS_Archive, path: FS_Path) -> Result; - pub fn FSUSER_OpenArchive(archive: *mut FS_Archive) -> Result; + pub fn FSUSER_OpenArchive(archive: *mut FS_Archive, id: FS_ArchiveID, + path: FS_Path) -> Result; pub fn FSUSER_ControlArchive(archive: FS_Archive, action: FS_ArchiveAction, - input: *mut c_void, - inputSize: u32, - output: *mut c_void, + input: *mut ::libc::c_void, inputSize: u32, + output: *mut ::libc::c_void, outputSize: u32) -> Result; - pub fn FSUSER_CloseArchive(archive: *mut FS_Archive) -> Result; + pub fn FSUSER_CloseArchive(archive: FS_Archive) -> Result; pub fn FSUSER_GetFreeBytes(freeBytes: *mut u64, archive: FS_Archive) -> Result; - pub fn FSUSER_GetCardType(_type: *mut FS_CardType) -> Result; + pub fn FSUSER_GetCardType(type_: *mut FS_CardType) -> Result; pub fn FSUSER_GetSdmcArchiveResource(archiveResource: *mut FS_ArchiveResource) -> Result; @@ -401,12 +393,13 @@ extern "C" { pub fn FSUSER_GetSpecialContentIndex(index: *mut u16, mediaType: FS_MediaType, programId: u64, - _type: FS_SpecialContentType) + type_: FS_SpecialContentType) -> Result; pub fn FSUSER_GetLegacyRomHeader(mediaType: FS_MediaType, programId: u64, header: *mut u8) -> Result; - pub fn FSUSER_GetLegacyBannerData(mediaType: FS_MediaType, programId: u64, - banner: *mut u8) -> Result; + pub fn FSUSER_GetLegacyBannerData(mediaType: FS_MediaType, + programId: u64, banner: *mut u8) + -> Result; pub fn FSUSER_CheckAuthorityToAccessExtSaveData(access: *mut u8, mediaType: FS_MediaType, saveId: u64, @@ -425,11 +418,12 @@ extern "C" { archiveId: FS_ArchiveID, path: FS_Path) -> Result; pub fn FSUSER_GetLegacyRomHeader2(headerSize: u32, - mediaType: FS_MediaType, programId: u64, - header: *mut u8) -> Result; + mediaType: FS_MediaType, + programId: u64, header: *mut u8) + -> Result; pub fn FSUSER_GetSdmcCtrRootPath(out: *mut u8, length: u32) -> Result; pub fn FSUSER_GetArchiveResource(archiveResource: *mut FS_ArchiveResource, - mediaType: FS_MediaType) -> Result; + mediaType: FS_SystemMediaType) -> Result; pub fn FSUSER_ExportIntegrityVerificationSeed(seed: *mut FS_IntegrityVerificationSeed) -> Result; @@ -482,13 +476,14 @@ extern "C" { emulateEndurance: u8) -> Result; pub fn FSUSER_SwitchCleanupInvalidSaveData(enable: u8) -> Result; pub fn FSUSER_EnumerateSystemSaveData(idsWritten: *mut u32, - idsSize: u32, ids: *mut u64) + idsSize: u32, ids: *mut u32) -> Result; pub fn FSUSER_InitializeWithSdkVersion(session: Handle, version: u32) -> Result; pub fn FSUSER_SetPriority(priority: u32) -> Result; pub fn FSUSER_GetPriority(priority: *mut u32) -> Result; - pub fn FSUSER_SetSaveDataSecureValue(value: u64, slot: FS_SecureValueSlot, + pub fn FSUSER_SetSaveDataSecureValue(value: u64, + slot: FS_SecureValueSlot, titleUniqueId: u32, titleVariation: u8) -> Result; pub fn FSUSER_GetSaveDataSecureValue(exists: *mut u8, value: *mut u64, @@ -496,22 +491,21 @@ extern "C" { titleUniqueId: u32, titleVariation: u8) -> Result; pub fn FSUSER_ControlSecureSave(action: FS_SecureSaveAction, - input: *mut c_void, + input: *mut ::libc::c_void, inputSize: u32, - output: *mut c_void, + output: *mut ::libc::c_void, outputSize: u32) -> Result; pub fn FSUSER_GetMediaType(mediaType: *mut FS_MediaType) -> Result; pub fn FSFILE_Control(handle: Handle, action: FS_FileAction, - input: *mut c_void, inputSize: u32, - output: *mut c_void, - outputSize: u32) -> Result; + input: *mut ::libc::c_void, inputSize: u32, + output: *mut ::libc::c_void, outputSize: u32) + -> Result; pub fn FSFILE_OpenSubFile(handle: Handle, subFile: *mut Handle, offset: u64, size: u64) -> Result; pub fn FSFILE_Read(handle: Handle, bytesRead: *mut u32, offset: u64, - buffer: *mut c_void, size: u32) - -> Result; + buffer: *mut ::libc::c_void, size: u32) -> Result; pub fn FSFILE_Write(handle: Handle, bytesWritten: *mut u32, offset: u64, - buffer: *const c_void, size: u32, + buffer: *const ::libc::c_void, size: u32, flags: u32) -> Result; pub fn FSFILE_GetSize(handle: Handle, size: *mut u64) -> Result; pub fn FSFILE_SetSize(handle: Handle, size: u64) -> Result; @@ -525,13 +519,18 @@ extern "C" { pub fn FSFILE_OpenLinkFile(handle: Handle, linkFile: *mut Handle) -> Result; pub fn FSDIR_Control(handle: Handle, action: FS_DirectoryAction, - input: *mut c_void, inputSize: u32, - output: *mut c_void, - outputSize: u32) -> Result; + input: *mut ::libc::c_void, inputSize: u32, + output: *mut ::libc::c_void, outputSize: u32) + -> Result; pub fn FSDIR_Read(handle: Handle, entriesRead: *mut u32, entryCount: u32, entries: *mut FS_DirectoryEntry) -> Result; pub fn FSDIR_Close(handle: Handle) -> Result; pub fn FSDIR_SetPriority(handle: Handle, priority: u32) -> Result; pub fn FSDIR_GetPriority(handle: Handle, priority: *mut u32) -> Result; + pub fn romfsMount(mount: *mut *mut romfs_mount) -> Result; + pub fn romfsMountFromFile(file: Handle, offset: u32, + mount: *mut *mut romfs_mount) -> Result; + pub fn romfsBind(mount: *mut romfs_mount) -> Result; + pub fn romfsUnmount(mount: *mut romfs_mount) -> Result; } From 282445c30a431e5162a555dd8d8284986759b953 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 12 Aug 2016 16:03:37 -0700 Subject: [PATCH 03/28] Add FS module --- src/services/fs.rs | 189 ++++++++++++++++++++++++++++++++++++++++++++ src/services/mod.rs | 1 + 2 files changed, 190 insertions(+) create mode 100644 src/services/fs.rs diff --git a/src/services/fs.rs b/src/services/fs.rs new file mode 100644 index 0000000..93db631 --- /dev/null +++ b/src/services/fs.rs @@ -0,0 +1,189 @@ +use core::marker::PhantomData; +use core::ptr; +use collections::Vec; + +use path::Path; + +use libctru::services::fs; + +pub struct Fs { + pd: PhantomData, +} + +impl Fs { + pub fn init() -> Result { + unsafe { + let r = fs::fsInit(); + if r < 0 { + Err(r) + } else { + Ok(Fs { pd: PhantomData }) + } + } + } + + pub fn sdmc(&self) -> Result { + let mut handle = 0u64; + unsafe { + let id = ArchiveID::Sdmc; + let path = fs::fsMakePath(PathType::Empty.into(), ptr::null() as *const _); + let ret = fs::FSUSER_OpenArchive(&mut handle, id.into(), path); + if ret < 0 { + Err(ret) + } else { + Ok(Archive { + handle: handle, + id: id, + }) + } + } + } +} + +impl Drop for Fs { + fn drop(&mut self) { + unsafe { + fs::fsExit(); + } + } +} + + +pub struct Archive { + id: ArchiveID, + handle: u64, +} + +impl Archive { + pub fn open_file(&self, p: &Path) -> Result { + unsafe { + let mut handle: u32 = 0; + let wide = p.as_os_str().encode_wide().collect::>(); + let path = fs::fsMakePath(PathType::UTF16.into(), wide.as_slice().as_ptr() as *mut _); + let ret = fs::FSUSER_OpenFile(&mut handle, self.handle, path, fs::FS_OPEN_READ, 0); + if ret < 0 { + Err(ret) + } else { + Ok(File { handle: handle }) + } + } + } + + pub fn id(&self) -> ArchiveID { + self.id + } +} + +impl Drop for Archive { + fn drop(&mut self) { + unsafe { + fs::FSUSER_CloseArchive(self.handle); + } + } +} + +pub struct File { + handle: u32, +} + +impl Drop for File { + fn drop(&mut self) { + unsafe { + fs::FSFILE_Close(self.handle); + } + } +} + +#[derive(Copy, Clone, Debug)] +pub enum PathType { + Invalid, + Empty, + Binary, + ASCII, + UTF16, +} + +impl From for fs::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 for PathType { + fn from(f: fs::FS_PathType) -> Self { + use self::PathType::*; + use libctru::services::fs::FS_PathType::*; + match f { + PATH_INVALID => Invalid, + PATH_EMPTY => Empty, + PATH_BINARY => Binary, + PATH_ASCII => ASCII, + PATH_UTF16 => 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, +} + +impl From for fs::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, + } + } +} diff --git a/src/services/mod.rs b/src/services/mod.rs index 734cac0..bb96484 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,4 +1,5 @@ pub mod apt; +pub mod fs; pub mod hid; pub mod gspgpu; From 9c2c664b63b522df408389b6d85190081143cda2 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sat, 13 Aug 2016 23:36:27 -0700 Subject: [PATCH 04/28] Add OpenOptions --- src/services/fs.rs | 183 +++++++++++++++++++++++++++++---------------- 1 file changed, 118 insertions(+), 65 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index 93db631..d868e71 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -4,16 +4,68 @@ use collections::Vec; use path::Path; -use libctru::services::fs; +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, +} + pub struct Fs { pd: PhantomData, } +pub struct Archive { + id: ArchiveID, + handle: u64, +} + +pub struct File { + handle: u32, +} + +pub struct OpenOptions { + read: bool, + write: bool, + create: bool, + arch_handle: u64, +} + impl Fs { pub fn init() -> Result { unsafe { - let r = fs::fsInit(); + let r = fsInit(); if r < 0 { Err(r) } else { @@ -26,8 +78,8 @@ impl Fs { let mut handle = 0u64; unsafe { let id = ArchiveID::Sdmc; - let path = fs::fsMakePath(PathType::Empty.into(), ptr::null() as *const _); - let ret = fs::FSUSER_OpenArchive(&mut handle, id.into(), path); + let path = fsMakePath(PathType::Empty.into(), ptr::null() as *const _); + let ret = FSUSER_OpenArchive(&mut handle, id.into(), path); if ret < 0 { Err(ret) } else { @@ -40,70 +92,97 @@ impl Fs { } } -impl Drop for Fs { - fn drop(&mut self) { - unsafe { - fs::fsExit(); +impl Archive { + pub fn file_open(&self, path: &Path) -> Result { + self.file_open_options().read(true).create(true).open(path) + } + + pub fn file_open_options(&self) -> OpenOptions { + OpenOptions { + read: false, + write: false, + create: false, + arch_handle: self.handle, } } + + pub fn id(&self) -> ArchiveID { + self.id + } } +impl OpenOptions { + pub fn read(&mut self, read: bool) -> &mut OpenOptions { + self.read = read; + self + } + + pub fn write(&mut self, write: bool) -> &mut OpenOptions { + self.write = write; + self + } -pub struct Archive { - id: ArchiveID, - handle: u64, -} + pub fn create(&mut self, create: bool) -> &mut OpenOptions { + self.create = create; + self + } -impl Archive { - pub fn open_file(&self, p: &Path) -> Result { + pub fn open>(&self, path: P) -> Result { + self._open(path.as_ref(), self.get_open_flags()) + } + + fn _open(&self, path: &Path, flags: u32) -> Result { unsafe { - let mut handle: u32 = 0; - let wide = p.as_os_str().encode_wide().collect::>(); - let path = fs::fsMakePath(PathType::UTF16.into(), wide.as_slice().as_ptr() as *mut _); - let ret = fs::FSUSER_OpenFile(&mut handle, self.handle, path, fs::FS_OPEN_READ, 0); + let mut file_handle: u32 = 0; + let wide = path.as_os_str().encode_wide().collect::>(); + let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_slice().as_ptr() as *mut _); + let ret = FSUSER_OpenFile(&mut file_handle, self.arch_handle, ctr_path, flags, 0); if ret < 0 { Err(ret) } else { - Ok(File { handle: handle }) + Ok(File { handle: file_handle }) } } } - pub fn id(&self) -> ArchiveID { - self.id + fn get_open_flags(&self) -> u32 { + match (self.read, self.write, self.create) { + (true, false, false) => FS_OPEN_READ, + (false, true, false) => FS_OPEN_WRITE, + (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 Drop for Archive { +impl Drop for Fs { fn drop(&mut self) { unsafe { - fs::FSUSER_CloseArchive(self.handle); + fsExit(); } } } -pub struct File { - handle: u32, -} -impl Drop for File { +impl Drop for Archive { fn drop(&mut self) { unsafe { - fs::FSFILE_Close(self.handle); + FSUSER_CloseArchive(self.handle); } } } -#[derive(Copy, Clone, Debug)] -pub enum PathType { - Invalid, - Empty, - Binary, - ASCII, - UTF16, +impl Drop for File { + fn drop(&mut self) { + unsafe { + FSFILE_Close(self.handle); + } + } } -impl From for fs::FS_PathType { +impl From for FS_PathType { fn from(p: PathType) -> Self { use self::PathType::*; use libctru::services::fs::FS_PathType::*; @@ -117,8 +196,8 @@ impl From for fs::FS_PathType { } } -impl From for PathType { - fn from(f: fs::FS_PathType) -> Self { +impl From for PathType { + fn from(f: FS_PathType) -> Self { use self::PathType::*; use libctru::services::fs::FS_PathType::*; match f { @@ -131,33 +210,7 @@ impl From for PathType { } } -#[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, -} - -impl From for fs::FS_ArchiveID { +impl From for FS_ArchiveID { fn from(a: ArchiveID) -> Self { use self::ArchiveID::*; use libctru::services::fs::FS_ArchiveID::*; From 96a2d13370e546ebc43461d0ae6ce51c0cf48afb Mon Sep 17 00:00:00 2001 From: Fenrir Date: Thu, 18 Aug 2016 23:16:57 -0700 Subject: [PATCH 05/28] minor cleanup --- src/services/fs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index d868e71..688341e 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -106,7 +106,7 @@ impl Archive { } } - pub fn id(&self) -> ArchiveID { + pub fn get_id(&self) -> ArchiveID { self.id } } @@ -135,7 +135,7 @@ impl OpenOptions { unsafe { let mut file_handle: u32 = 0; let wide = path.as_os_str().encode_wide().collect::>(); - let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_slice().as_ptr() as *mut _); + let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_ptr() as *mut _); let ret = FSUSER_OpenFile(&mut file_handle, self.arch_handle, ctr_path, flags, 0); if ret < 0 { Err(ret) From ca3658108f48e10d560f1bb6d73513c93e0b8656 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Thu, 18 Aug 2016 23:45:46 -0700 Subject: [PATCH 06/28] Delete functions that were removed from ctrulib --- src/services/apt.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/services/apt.rs b/src/services/apt.rs index 28e0500..69094b2 100644 --- a/src/services/apt.rs +++ b/src/services/apt.rs @@ -66,26 +66,6 @@ impl Apt { } } - - pub fn get_status(&self) -> AppStatus { - unsafe { apt::aptGetStatus().into() } - } - - pub fn set_status(&mut self, status: AppStatus) { - unsafe { apt::aptSetStatus(status.into()) }; - } - - /// Return to the home menu. - /// - /// When `get_status` returns `AppStatus::Suspending`, you should call this, - /// otherwise the app will be left stuck in that state. - /// - /// The program will not return from this function until the system returns - /// to the application, or when the status changes to `AppStatus::Exiting`. - pub fn return_to_menu(&mut self) { - unsafe { apt::aptReturnToMenu() } - } - pub fn main_loop(&self) -> bool { unsafe { match apt::aptMainLoop() { From 94e927f967c1d31b4ec379445104aef2d4c90e91 Mon Sep 17 00:00:00 2001 From: panicbit Date: Sat, 20 Aug 2016 04:35:56 +0200 Subject: [PATCH 07/28] Add read method to fs File --- src/services/fs.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index 688341e..247a5b1 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -53,6 +53,7 @@ pub struct Archive { pub struct File { handle: u32, + offset: u64, } pub struct OpenOptions { @@ -111,6 +112,29 @@ impl Archive { } } +impl File { + pub fn read(&mut self, buf: &mut [u8]) -> Result { + 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) + } + } + } +} + impl OpenOptions { pub fn read(&mut self, read: bool) -> &mut OpenOptions { self.read = read; @@ -140,7 +164,10 @@ impl OpenOptions { if ret < 0 { Err(ret) } else { - Ok(File { handle: file_handle }) + Ok(File { + handle: file_handle, + offset: 0, + }) } } } From d604db441014c2270c9fc63a08337996f0a92a2d Mon Sep 17 00:00:00 2001 From: panicbit Date: Sat, 20 Aug 2016 04:36:14 +0200 Subject: [PATCH 08/28] Add len method to fs File --- src/services/fs.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/services/fs.rs b/src/services/fs.rs index 247a5b1..ac84667 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -113,6 +113,18 @@ impl Archive { } impl File { + pub fn len(&self) -> Result { + unsafe { + let mut len = 0; + let r = FSFILE_GetSize(self.handle, &mut len); + if r < 0 { + Err(r) + } else { + Ok(len) + } + } + } + pub fn read(&mut self, buf: &mut [u8]) -> Result { unsafe { let mut n_read = 0; From b68d02baa3938e2ec4d0af71952c917ba1ea8901 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Thu, 15 Sep 2016 22:55:17 -0700 Subject: [PATCH 09/28] Add write method to fs File --- src/services/fs.rs | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index ac84667..ddca72c 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -79,7 +79,7 @@ impl Fs { let mut handle = 0u64; unsafe { let id = ArchiveID::Sdmc; - let path = fsMakePath(PathType::Empty.into(), ptr::null() as *const _); + let path = fsMakePath(PathType::Empty.into(), ptr::null() as _); let ret = FSUSER_OpenArchive(&mut handle, id.into(), path); if ret < 0 { Err(ret) @@ -125,7 +125,7 @@ impl File { } } - pub fn read(&mut self, buf: &mut [u8]) -> Result { + pub fn read(&mut self, buf: &mut [u8]) -> Result { unsafe { let mut n_read = 0; let r = FSFILE_Read( @@ -135,13 +135,31 @@ impl File { 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) + } + } + } + pub fn write(&mut self, buf: &[u8]) -> Result { + 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_FLUSH | FS_WRITE_UPDATE_TIME + ); + self.offset += n_written as u64; if r < 0 { Err(r) } else { - Ok(n_read) + Ok(n_written as usize) } } } @@ -169,9 +187,9 @@ impl OpenOptions { fn _open(&self, path: &Path, flags: u32) -> Result { unsafe { - let mut file_handle: u32 = 0; + let mut file_handle = 0; let wide = path.as_os_str().encode_wide().collect::>(); - let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_ptr() as *mut _); + let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_ptr() as _); let ret = FSUSER_OpenFile(&mut file_handle, self.arch_handle, ctr_path, flags, 0); if ret < 0 { Err(ret) @@ -188,6 +206,7 @@ impl OpenOptions { match (self.read, self.write, 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, From edd21380c37d3f5af5aa85c14186f3af154241c7 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 19 Sep 2016 22:22:36 -0700 Subject: [PATCH 10/28] Refactor File and OpenOptions methods --- src/services/fs.rs | 64 +++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index ddca72c..c19e568 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -56,6 +56,7 @@ pub struct File { offset: u64, } +#[derive(Clone)] pub struct OpenOptions { read: bool, write: bool, @@ -76,7 +77,7 @@ impl Fs { } pub fn sdmc(&self) -> Result { - let mut handle = 0u64; + let mut handle = 0; unsafe { let id = ArchiveID::Sdmc; let path = fsMakePath(PathType::Empty.into(), ptr::null() as _); @@ -94,25 +95,20 @@ impl Fs { } impl Archive { - pub fn file_open(&self, path: &Path) -> Result { - self.file_open_options().read(true).create(true).open(path) - } - - pub fn file_open_options(&self) -> OpenOptions { - OpenOptions { - read: false, - write: false, - create: false, - arch_handle: self.handle, - } - } - pub fn get_id(&self) -> ArchiveID { self.id } } impl File { + pub fn open>(arch: &Archive, path: P) -> Result { + OpenOptions::new().read(true).archive(arch).open(path) + } + + pub fn create>(arch: &Archive, path: P) -> Result { + OpenOptions::new().write(true).create(true).archive(arch).open(path) + } + pub fn len(&self) -> Result { unsafe { let mut len = 0; @@ -125,6 +121,17 @@ impl File { } } + pub fn set_len(&mut self, len: u64) -> Result<(), i32> { + unsafe { + let r = FSFILE_SetSize(self.handle, len); + if r < 0 { + Err(r) + } else { + Ok(()) + } + } + } + pub fn read(&mut self, buf: &mut [u8]) -> Result { unsafe { let mut n_read = 0; @@ -166,6 +173,15 @@ impl File { } impl OpenOptions { + pub fn new() -> OpenOptions { + OpenOptions { + read: false, + write: false, + create: false, + arch_handle: 0, + } + } + pub fn read(&mut self, read: bool) -> &mut OpenOptions { self.read = read; self @@ -181,6 +197,11 @@ impl OpenOptions { self } + pub fn archive(&mut self, archive: &Archive) -> &mut OpenOptions { + self.arch_handle = archive.handle; + self + } + pub fn open>(&self, path: P) -> Result { self._open(path.as_ref(), self.get_open_flags()) } @@ -223,7 +244,6 @@ impl Drop for Fs { } } - impl Drop for Archive { fn drop(&mut self) { unsafe { @@ -254,20 +274,6 @@ impl From for FS_PathType { } } -impl From for PathType { - fn from(f: FS_PathType) -> Self { - use self::PathType::*; - use libctru::services::fs::FS_PathType::*; - match f { - PATH_INVALID => Invalid, - PATH_EMPTY => Empty, - PATH_BINARY => Binary, - PATH_ASCII => ASCII, - PATH_UTF16 => UTF16, - } - } -} - impl From for FS_ArchiveID { fn from(a: ArchiveID) -> Self { use self::ArchiveID::*; From 0db7db3f8d1b3689aa525d0010b6ffa73abde850 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 19 Sep 2016 22:22:54 -0700 Subject: [PATCH 11/28] Update 3ds.json --- 3ds.json | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/3ds.json b/3ds.json index 3255f2a..fbdf65d 100644 --- a/3ds.json +++ b/3ds.json @@ -1,30 +1,32 @@ { "data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64", "llvm-target": "arm-none-eabihf", - "linker": "arm-none-eabi-g++", + "linker": "arm-none-eabi-gcc", "ar": "arm-none-eabi-ar", "target-endian": "little", "target-pointer-width": "32", "arch": "arm", - "os": "none", + "os": "linux", "cpu": "mpcore", "features": "+vfp2", "relocation-model": "static", - "linker-is-gnu": true, - "has-rpath": true, - "morestack": false, "disable-redzone": true, "executables": true, - "dynamic-linking": false, "no-compiler-rt": true, "exe-suffix": ".elf", - "is-like-windows": true, - "function-sections": false, "pre-link-args": [ - "-specs", - "3dsx.specs", + "-specs=3dsx.specs", "-march=armv6k", "-mtune=mpcore", - "-mfloat-abi=hard" + "-mfloat-abi=hard", + "-mtp=soft" + ], + "post-link-args": [ + "-lc", + "-lm", + "-lsysbase", + "-lc", + "-lgcc", + "-lc" ] } From 2836bc3c6e5392e48a16af85c14217c49e5baae1 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 21 Sep 2016 16:26:09 -0700 Subject: [PATCH 12/28] Add remove_file function --- src/services/fs.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index c19e568..188fe4c 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -77,13 +77,13 @@ impl Fs { } pub fn sdmc(&self) -> Result { - let mut handle = 0; unsafe { + let mut handle = 0; let id = ArchiveID::Sdmc; let path = fsMakePath(PathType::Empty.into(), ptr::null() as _); - let ret = FSUSER_OpenArchive(&mut handle, id.into(), path); - if ret < 0 { - Err(ret) + let r = FSUSER_OpenArchive(&mut handle, id.into(), path); + if r < 0 { + Err(r) } else { Ok(Archive { handle: handle, @@ -211,9 +211,9 @@ impl OpenOptions { let mut file_handle = 0; let wide = path.as_os_str().encode_wide().collect::>(); let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_ptr() as _); - let ret = FSUSER_OpenFile(&mut file_handle, self.arch_handle, ctr_path, flags, 0); - if ret < 0 { - Err(ret) + let r = FSUSER_OpenFile(&mut file_handle, self.arch_handle, ctr_path, flags, 0); + if r < 0 { + Err(r) } else { Ok(File { handle: file_handle, @@ -236,6 +236,19 @@ impl OpenOptions { } } +pub fn remove_file>(arch: &Archive, path: P) -> Result<(), i32> { + unsafe { + let wide = path.as_ref().as_os_str().encode_wide().collect::>(); + let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_ptr() as _); + let r = FSUSER_DeleteFile(arch.handle, ctr_path); + if r < 0 { + Err(r) + } else { + Ok(()) + } + } +} + impl Drop for Fs { fn drop(&mut self) { unsafe { From 1a99cf387fb2aa7ff35879a26f13bff147a2e88f Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 21 Sep 2016 20:40:01 -0700 Subject: [PATCH 13/28] Add read_to_end function --- src/services/fs.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/services/fs.rs b/src/services/fs.rs index 188fe4c..8a0829e 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -1,5 +1,6 @@ use core::marker::PhantomData; use core::ptr; +use core::slice; use collections::Vec; use path::Path; @@ -151,6 +152,12 @@ impl File { } } + pub fn read_to_end(&mut self, buf: &mut Vec) -> Result { + unsafe { + read_to_end_uninitialized(self, buf) + } + } + pub fn write(&mut self, buf: &[u8]) -> Result { unsafe { let mut n_written = 0; @@ -249,6 +256,33 @@ pub fn remove_file>(arch: &Archive, path: P) -> Result<(), i32> { } } +// Adapted from sys/common/io.rs in libstd +unsafe fn read_to_end_uninitialized(f: &mut File, buf: &mut Vec) -> Result { + 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 { From 6c178aa1da4864377ccdf79a9e83428b68ebbddc Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 21 Sep 2016 23:45:35 -0700 Subject: [PATCH 14/28] Add alloc_system dependency --- Cargo.toml | 3 +++ src/lib.rs | 1 + 2 files changed, 4 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 0aa1ecf..f95ca35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ version = "0.4.0" [dependencies.ctru-sys] path = "ctru-sys" +[dependencies.alloc_system3ds] +git = "https://github.com/rust3ds/alloc_system3ds" + [lib] crate-type = ["rlib"] name = "ctru" diff --git a/src/lib.rs b/src/lib.rs index 7fa8597..14702af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ #![crate_name = "ctru"] extern crate alloc; +extern crate alloc_system; extern crate collections; extern crate rustc_unicode; From d28c7affd45f804c11b48a415b8532064ae9d10c Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 23 Sep 2016 13:18:14 -0700 Subject: [PATCH 15/28] Prolly shouldn't flush on every write --- src/services/fs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index 8a0829e..6fbdb62 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -167,7 +167,7 @@ impl File { self.offset, buf.as_ptr() as _, buf.len() as u32, - FS_WRITE_FLUSH | FS_WRITE_UPDATE_TIME + FS_WRITE_UPDATE_TIME ); self.offset += n_written as u64; if r < 0 { From 93760035f71b77949eff3fd876fd24d9d52c32f3 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 23 Sep 2016 13:16:47 -0700 Subject: [PATCH 16/28] Add directory create and delete functions --- src/services/fs.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/services/fs.rs b/src/services/fs.rs index 6fbdb62..e088a6e 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -243,6 +243,45 @@ impl OpenOptions { } } +pub fn create_dir>(arch: &Archive, path: P) -> Result<(), i32> { + unsafe { + let wide = path.as_ref().as_os_str().encode_wide().collect::>(); + let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_ptr() as _); + let r = FSUSER_CreateDirectory(arch.handle, ctr_path, FS_ATTRIBUTE_DIRECTORY); + if r < 0 { + Err(r) + } else { + Ok(()) + } + } +} + +pub fn remove_dir>(arch: &Archive, path: P) -> Result<(), i32> { + unsafe { + let wide = path.as_ref().as_os_str().encode_wide().collect::>(); + let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_ptr() as _); + let r = FSUSER_DeleteDirectory(arch.handle, ctr_path); + if r < 0 { + Err(r) + } else { + Ok(()) + } + } +} + +pub fn remove_dir_all>(arch: &Archive, path: P) -> Result<(), i32> { + unsafe { + let wide = path.as_ref().as_os_str().encode_wide().collect::>(); + let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_ptr() as _); + let r = FSUSER_DeleteDirectoryRecursively(arch.handle, ctr_path); + if r < 0 { + Err(r) + } else { + Ok(()) + } + } +} + pub fn remove_file>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { let wide = path.as_ref().as_os_str().encode_wide().collect::>(); From 6544900c4c182667386a583a40a47e0fc57d4649 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sat, 24 Sep 2016 21:15:27 -0700 Subject: [PATCH 17/28] Add rename function --- src/services/fs.rs | 59 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index e088a6e..a03fe92 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -216,9 +216,9 @@ impl OpenOptions { fn _open(&self, path: &Path, flags: u32) -> Result { unsafe { let mut file_handle = 0; - let wide = path.as_os_str().encode_wide().collect::>(); - let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_ptr() as _); - let r = FSUSER_OpenFile(&mut file_handle, self.arch_handle, ctr_path, flags, 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 { Err(r) } else { @@ -245,9 +245,9 @@ impl OpenOptions { pub fn create_dir>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { - let wide = path.as_ref().as_os_str().encode_wide().collect::>(); - let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_ptr() as _); - let r = FSUSER_CreateDirectory(arch.handle, ctr_path, FS_ATTRIBUTE_DIRECTORY); + let path = to_utf16(path); + 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 { @@ -258,9 +258,9 @@ pub fn create_dir>(arch: &Archive, path: P) -> Result<(), i32> { pub fn remove_dir>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { - let wide = path.as_ref().as_os_str().encode_wide().collect::>(); - let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_ptr() as _); - let r = FSUSER_DeleteDirectory(arch.handle, ctr_path); + let path = to_utf16(path); + 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 { @@ -271,9 +271,9 @@ pub fn remove_dir>(arch: &Archive, path: P) -> Result<(), i32> { pub fn remove_dir_all>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { - let wide = path.as_ref().as_os_str().encode_wide().collect::>(); - let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_ptr() as _); - let r = FSUSER_DeleteDirectoryRecursively(arch.handle, ctr_path); + let path = to_utf16(path); + 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 { @@ -284,9 +284,9 @@ pub fn remove_dir_all>(arch: &Archive, path: P) -> Result<(), i32 pub fn remove_file>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { - let wide = path.as_ref().as_os_str().encode_wide().collect::>(); - let ctr_path = fsMakePath(PathType::UTF16.into(), wide.as_ptr() as _); - let r = FSUSER_DeleteFile(arch.handle, ctr_path); + let path = to_utf16(path); + 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 { @@ -295,6 +295,35 @@ pub fn remove_file>(arch: &Archive, path: P) -> Result<(), i32> { } } +pub fn rename(arch: &Archive, from: P, to: Q) -> Result<(), i32> + where P: AsRef, + Q: AsRef { + + unsafe { + let from = to_utf16(from); + let to = to_utf16(to); + + 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 interior NULLs are premitted in 3DS file paths +fn to_utf16>(path: S) -> Vec { + path.as_ref().as_os_str().encode_wide().collect::>() +} + + // Adapted from sys/common/io.rs in libstd unsafe fn read_to_end_uninitialized(f: &mut File, buf: &mut Vec) -> Result { let start_len = buf.len(); From e7f87b9d8fe1a2131212107f3df2ab162b9c2ff2 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 26 Sep 2016 06:17:50 -0700 Subject: [PATCH 18/28] Add read_dir function (directory iteration!) --- src/services/fs.rs | 110 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 99 insertions(+), 11 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index a03fe92..fabacfa 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -1,9 +1,12 @@ use core::marker::PhantomData; use core::ptr; use core::slice; +use core::mem; +use alloc::arc::Arc; use collections::Vec; -use path::Path; +use path::{Path, PathBuf}; +use ffi::OsString; use libctru::services::fs::*; @@ -65,6 +68,21 @@ pub struct OpenOptions { arch_handle: u64, } +pub struct ReadDir { + handle: Dir, + root: Arc, +} + +pub struct DirEntry { + root: Arc, + entry: FS_DirectoryEntry, +} + +struct Dir(u32); + +unsafe impl Send for Dir {} +unsafe impl Sync for Dir {} + impl Fs { pub fn init() -> Result { unsafe { @@ -103,11 +121,11 @@ impl Archive { impl File { pub fn open>(arch: &Archive, path: P) -> Result { - OpenOptions::new().read(true).archive(arch).open(path) + OpenOptions::new().read(true).archive(arch).open(path.as_ref()) } pub fn create>(arch: &Archive, path: P) -> Result { - OpenOptions::new().write(true).create(true).archive(arch).open(path) + OpenOptions::new().write(true).create(true).archive(arch).open(path.as_ref()) } pub fn len(&self) -> Result { @@ -243,9 +261,44 @@ impl OpenOptions { } } +impl Iterator for ReadDir { + type Item = Result; + + fn next(&mut self) -> Option> { + unsafe { + let mut ret = DirEntry { + entry: mem::zeroed(), + root: self.root.clone(), + }; + 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 DirEntry { + pub fn path(&self) -> PathBuf { + self.root.join(&self.file_name()) + } + + pub fn file_name(&self) -> OsString { + let filename = truncate_utf16_at_nul(&self.entry.name); + OsString::from_wide(filename) + } +} + pub fn create_dir>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { - let path = to_utf16(path); + 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 { @@ -258,7 +311,7 @@ pub fn create_dir>(arch: &Archive, path: P) -> Result<(), i32> { pub fn remove_dir>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { - let path = to_utf16(path); + 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 { @@ -271,7 +324,7 @@ pub fn remove_dir>(arch: &Archive, path: P) -> Result<(), i32> { pub fn remove_dir_all>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { - let path = to_utf16(path); + 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 { @@ -282,9 +335,13 @@ pub fn remove_dir_all>(arch: &Archive, path: P) -> Result<(), i32 } } +pub fn read_dir>(arch: &Archive, path: P) -> Result { + readdir(&arch, path.as_ref()) +} + pub fn remove_file>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { - let path = to_utf16(path); + 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 { @@ -300,8 +357,8 @@ pub fn rename(arch: &Archive, from: P, to: Q) -> Result<(), i32> Q: AsRef { unsafe { - let from = to_utf16(from); - let to = to_utf16(to); + 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 _); @@ -318,11 +375,34 @@ pub fn rename(arch: &Archive, from: P, to: Q) -> Result<(), i32> } } +fn readdir(arch: &Archive, p: &Path) -> Result { + unsafe { + let mut handle = 0; + let root = Arc::new(p.to_path_buf()); + let path = to_utf16(p); + 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 }) + } + } +} + // TODO: Determine if interior NULLs are premitted in 3DS file paths -fn to_utf16>(path: S) -> Vec { - path.as_ref().as_os_str().encode_wide().collect::>() +fn to_utf16(path: &Path) -> Vec { + path.as_os_str().encode_wide().collect::>() } +// 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) -> Result { @@ -375,6 +455,14 @@ impl Drop for File { } } +impl Drop for Dir { + fn drop(&mut self) { + unsafe { + FSDIR_Close(self.0); + } + } +} + impl From for FS_PathType { fn from(p: PathType) -> Self { use self::PathType::*; From 6ab8bd0efd7b8adcf04d8d39e54e20f2cc8b9a4d Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 27 Sep 2016 23:46:48 -0700 Subject: [PATCH 19/28] Add metadata struct and functions --- src/services/fs.rs | 55 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index fabacfa..a01ae73 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -60,6 +60,11 @@ pub struct File { offset: u64, } +pub struct Metadata { + attributes: u32, + size: u64, +} + #[derive(Clone)] pub struct OpenOptions { read: bool, @@ -74,8 +79,8 @@ pub struct ReadDir { } pub struct DirEntry { - root: Arc, entry: FS_DirectoryEntry, + root: Arc, } struct Dir(u32); @@ -128,25 +133,27 @@ impl File { OpenOptions::new().write(true).create(true).archive(arch).open(path.as_ref()) } - pub fn len(&self) -> Result { + pub fn set_len(&mut self, len: u64) -> Result<(), i32> { unsafe { - let mut len = 0; - let r = FSFILE_GetSize(self.handle, &mut len); + let r = FSFILE_SetSize(self.handle, len); if r < 0 { Err(r) } else { - Ok(len) + Ok(()) } } } - pub fn set_len(&mut self, len: u64) -> Result<(), i32> { + // Right now the only file metadata we really have is file size + // This will probably expand later on + pub fn metadata(&self) -> Result { unsafe { - let r = FSFILE_SetSize(self.handle, len); + let mut size = 0; + let r = FSFILE_GetSize(self.handle, &mut size); if r < 0 { Err(r) } else { - Ok(()) + Ok(Metadata { attributes: 0, size: size }) } } } @@ -197,6 +204,20 @@ impl File { } } +impl Metadata { + pub fn is_dir(&self) -> bool { + self.attributes == self.attributes | FS_ATTRIBUTE_DIRECTORY + } + + pub fn is_file(&self) -> bool { + !self.is_dir() + } + + pub fn len(&self) -> u64 { + self.size + } +} + impl OpenOptions { pub fn new() -> OpenOptions { OpenOptions { @@ -290,6 +311,12 @@ impl DirEntry { self.root.join(&self.file_name()) } + // Requiring the user to explicitly pass in the Archive here is pretty ugly, + // But I'm not sure of how else to do it right now. + pub fn metadata(&self, arch: &Archive) -> Result { + metadata(&arch, self.path()) + } + pub fn file_name(&self) -> OsString { let filename = truncate_utf16_at_nul(&self.entry.name); OsString::from_wide(filename) @@ -309,6 +336,16 @@ pub fn create_dir>(arch: &Archive, path: P) -> Result<(), i32> { } } +pub fn metadata>(arch: &Archive, path: P) -> Result { + 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), + } +} + pub fn remove_dir>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { let path = to_utf16(path.as_ref()); @@ -390,7 +427,7 @@ fn readdir(arch: &Archive, p: &Path) -> Result { } } -// TODO: Determine if interior NULLs are premitted in 3DS file paths +// TODO: Determine if we should check UTF-16 paths for interior NULs fn to_utf16(path: &Path) -> Vec { path.as_os_str().encode_wide().collect::>() } From 5e01a58b84d47b20d020d12c69799ec8db365274 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 28 Sep 2016 01:25:44 -0700 Subject: [PATCH 20/28] Fix DirEntry's metadata() method --- src/services/fs.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index a01ae73..aa07220 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -73,14 +73,16 @@ pub struct OpenOptions { arch_handle: u64, } -pub struct ReadDir { +pub struct ReadDir<'a> { handle: Dir, root: Arc, + arch: &'a Archive, } -pub struct DirEntry { +pub struct DirEntry<'a> { entry: FS_DirectoryEntry, root: Arc, + arch: &'a Archive, } struct Dir(u32); @@ -282,14 +284,15 @@ impl OpenOptions { } } -impl Iterator for ReadDir { - type Item = Result; +impl<'a> Iterator for ReadDir<'a> { + type Item = Result, i32>; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option, 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; @@ -306,15 +309,13 @@ impl Iterator for ReadDir { } } -impl DirEntry { +impl<'a> DirEntry<'a> { pub fn path(&self) -> PathBuf { self.root.join(&self.file_name()) } - // Requiring the user to explicitly pass in the Archive here is pretty ugly, - // But I'm not sure of how else to do it right now. - pub fn metadata(&self, arch: &Archive) -> Result { - metadata(&arch, self.path()) + pub fn metadata(&self) -> Result { + metadata(self.arch, self.path()) } pub fn file_name(&self) -> OsString { @@ -412,7 +413,7 @@ pub fn rename(arch: &Archive, from: P, to: Q) -> Result<(), i32> } } -fn readdir(arch: &Archive, p: &Path) -> Result { +fn readdir<'a>(arch: &'a Archive, p: &Path) -> Result, i32> { unsafe { let mut handle = 0; let root = Arc::new(p.to_path_buf()); @@ -422,7 +423,7 @@ fn readdir(arch: &Archive, p: &Path) -> Result { if r < 0 { Err(r) } else { - Ok(ReadDir { handle: Dir(handle), root: root }) + Ok(ReadDir { handle: Dir(handle), root: root, arch: arch}) } } } From 1fb45b4309d68fd27ce6d4b6b08123679c99b471 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Thu, 29 Sep 2016 03:30:01 -0700 Subject: [PATCH 21/28] Add create_dir_all function --- src/services/fs.rs | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index aa07220..01df070 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -337,6 +337,19 @@ pub fn create_dir>(arch: &Archive, path: P) -> Result<(), i32> { } } +pub fn create_dir_all>(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 +} + pub fn metadata>(arch: &Archive, path: P) -> Result { let maybe_file = File::open(&arch, path.as_ref()); let maybe_dir = read_dir(&arch, path.as_ref()); @@ -374,7 +387,18 @@ pub fn remove_dir_all>(arch: &Archive, path: P) -> Result<(), i32 } pub fn read_dir>(arch: &Archive, path: P) -> Result { - readdir(&arch, path.as_ref()) + 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}) + } + } } pub fn remove_file>(arch: &Archive, path: P) -> Result<(), i32> { @@ -413,21 +437,6 @@ pub fn rename(arch: &Archive, from: P, to: Q) -> Result<(), i32> } } -fn readdir<'a>(arch: &'a Archive, p: &Path) -> Result, i32> { - unsafe { - let mut handle = 0; - let root = Arc::new(p.to_path_buf()); - let path = to_utf16(p); - 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}) - } - } -} - // TODO: Determine if we should check UTF-16 paths for interior NULs fn to_utf16(path: &Path) -> Vec { path.as_os_str().encode_wide().collect::>() From ed0c883b431033fe48aa251831c1358838c783c1 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Thu, 29 Sep 2016 15:16:45 -0700 Subject: [PATCH 22/28] Documentation for the FS service --- src/services/fs.rs | 303 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 298 insertions(+), 5 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index 01df070..fb8309c 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -1,3 +1,9 @@ +//! 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; @@ -45,26 +51,106 @@ pub enum ArchiveID { DemoSavedata, } - +/// Represents the filesystem service. No file IO can be performed +/// until an instance of this struct is created. +/// +/// The service exits when this struct goes out of scope. pub struct Fs { pd: PhantomData, } +/// 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, @@ -73,24 +159,55 @@ pub struct OpenOptions { 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`. 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, 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, 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. This typically reflects a problem with the execution + /// environment and not necessarily your program itself. pub fn init() -> Result { unsafe { let r = fsInit(); @@ -102,6 +219,7 @@ impl Fs { } } + /// Returns a handle to the SDMC (memory card) Archive. pub fn sdmc(&self) -> Result { unsafe { let mut handle = 0; @@ -121,23 +239,73 @@ impl Fs { } 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::servies::fs::{Fs, File}; + /// + /// let fs = Fs::init().unwrap() + /// let sdmc_archive = fs.sdmc().unwrap() + /// let mut f = File::open("/foo.txt").unwrap(); + /// ``` pub fn open>(arch: &Archive, path: P) -> Result { 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::servies::fs::{Fs, File}; + /// + /// let fs = Fs::init().unwrap() + /// let sdmc_archive = fs.sdmc().unwrap() + /// let mut f = File::create("/foo.txt").unwrap(); + /// ``` pub fn create>(arch: &Archive, path: P) -> Result { OpenOptions::new().write(true).create(true).archive(arch).open(path.as_ref()) } - pub fn set_len(&mut self, len: u64) -> Result<(), i32> { + /// 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, len); + let r = FSFILE_SetSize(self.handle, size); if r < 0 { Err(r) } else { @@ -146,9 +314,10 @@ impl File { } } - // Right now the only file metadata we really have is file size - // This will probably expand later on + /// Queries metadata about the underlying file. pub fn metadata(&self) -> Result { + // 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); @@ -160,6 +329,11 @@ impl File { } } + /// 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 { unsafe { let mut n_read = 0; @@ -179,12 +353,20 @@ impl File { } } + /// 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) -> Result { 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 { unsafe { let mut n_written = 0; @@ -207,20 +389,28 @@ impl File { } 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, @@ -230,26 +420,61 @@ impl OpenOptions { } } + /// 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 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>(&self, path: P) -> Result { self._open(path.as_ref(), self.get_open_flags()) } @@ -310,20 +535,34 @@ impl<'a> Iterator for ReadDir<'a> { } 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(self.arch, self.path()) } + /// Return the file type for the file that this entry points at. 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>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { let path = to_utf16(path.as_ref()); @@ -337,6 +576,15 @@ pub fn create_dir>(arch: &Archive, path: P) -> Result<(), i32> { } } +/// 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>(arch: &Archive, path: P) -> Result<(), i32> { let path = path.as_ref(); let mut dir = PathBuf::new(); @@ -350,6 +598,7 @@ pub fn create_dir_all>(arch: &Archive, path: P) -> Result<(), i32 result } +/// Given a path, query the file system to get information about a file, directory, etc pub fn metadata>(arch: &Archive, path: P) -> Result { let maybe_file = File::open(&arch, path.as_ref()); let maybe_dir = read_dir(&arch, path.as_ref()); @@ -360,6 +609,15 @@ pub fn metadata>(arch: &Archive, path: P) -> Result>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { let path = to_utf16(path.as_ref()); @@ -373,6 +631,11 @@ pub fn remove_dir>(arch: &Archive, path: P) -> Result<(), i32> { } } +/// 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>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { let path = to_utf16(path.as_ref()); @@ -386,6 +649,17 @@ pub fn remove_dir_all>(arch: &Archive, path: P) -> Result<(), i32 } } +/// Returns an iterator over the entries within a directory. +/// +/// The iterator will yield instances of Result. 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>(arch: &Archive, path: P) -> Result { unsafe { let mut handle = 0; @@ -401,6 +675,15 @@ pub fn read_dir>(arch: &Archive, path: P) -> Result } } +/// 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>(arch: &Archive, path: P) -> Result<(), i32> { unsafe { let path = to_utf16(path.as_ref()); @@ -414,6 +697,16 @@ pub fn remove_file>(arch: &Archive, path: P) -> Result<(), i32> { } } +/// 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(arch: &Archive, from: P, to: Q) -> Result<(), i32> where P: AsRef, Q: AsRef { From 3987b78ed38d2241fdebb554597ac37bcc699f8a Mon Sep 17 00:00:00 2001 From: Fenrir Date: Thu, 29 Sep 2016 20:40:22 -0700 Subject: [PATCH 23/28] Add append and truncate modes to OpenOptions Also, fix an incorrect method description for DirEntry --- src/services/fs.rs | 59 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index fb8309c..ef4949a 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -155,6 +155,8 @@ pub struct Metadata { pub struct OpenOptions { read: bool, write: bool, + append: bool, + truncate: bool, create: bool, arch_handle: u64, } @@ -415,6 +417,8 @@ impl OpenOptions { OpenOptions { read: false, write: false, + append: false, + truncate: false, create: false, arch_handle: 0, } @@ -441,6 +445,31 @@ impl OpenOptions { 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 { + // we're going to be cheeky and just manually set write access here + self.append = append; + self.write = 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 @@ -486,13 +515,28 @@ impl OpenOptions { 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 { - Err(r) - } else { - Ok(File { - handle: file_handle, - offset: 0, - }) + return Err(r); } + + let mut file = File { handle: file_handle, offset: 0 }; + + // We have write access if append is true, so we *should* be + // fine unwrapping here + if self.append { + file.offset = file.metadata().unwrap().len(); + } + + // we might not have write access even if truncate is true, + // so let's use try! + // + // But let's also set the offset to 0 just in case both + // append and truncate are true + if self.truncate { + try!(file.set_len(0)); + file.offset = 0; + } + + Ok(file) } } @@ -548,7 +592,8 @@ impl<'a> DirEntry<'a> { metadata(self.arch, self.path()) } - /// Return the file type for the file that this entry points at. + /// 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) From c3da19784f631c0492a1d61f1bb0b1353cdd1e5d Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 30 Sep 2016 00:06:01 -0700 Subject: [PATCH 24/28] Remove unwrap from append logic --- src/services/fs.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index ef4949a..6bca22a 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -520,22 +520,17 @@ impl OpenOptions { let mut file = File { handle: file_handle, offset: 0 }; - // We have write access if append is true, so we *should* be - // fine unwrapping here if self.append { - file.offset = file.metadata().unwrap().len(); + let metadata = try!(file.metadata()); + file.offset = metadata.len(); } - // we might not have write access even if truncate is true, - // so let's use try! - // - // But let's also set the offset to 0 just in case both - // append and truncate are true + // 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) } } From bef90b4c04b6c538dad866149a881acd283fc93d Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sun, 2 Oct 2016 21:28:38 -0700 Subject: [PATCH 25/28] update unicode methods in wtf8 module --- src/sys/wtf8.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/sys/wtf8.rs b/src/sys/wtf8.rs index 25c21c7..12ca7a1 100644 --- a/src/sys/wtf8.rs +++ b/src/sys/wtf8.rs @@ -206,8 +206,12 @@ impl Wtf8Buf { /// Copied from String::push /// This does **not** include the WTF-8 concatenation check. fn push_code_point_unchecked(&mut self, code_point: CodePoint) { - let bytes = unsafe { char::from_u32_unchecked(code_point.value).encode_utf8() }; - self.bytes.extend_from_slice(bytes.as_slice()); + let c = unsafe { + char::from_u32_unchecked(code_point.value) + }; + let mut bytes = [0; 4]; + let bytes = c.encode_utf8(&mut bytes).as_bytes(); + self.bytes.extend_from_slice(bytes) } #[inline] @@ -736,13 +740,16 @@ impl<'a> Iterator for EncodeWide<'a> { return Some(tmp); } + let mut buf = [0; 2]; self.code_points.next().map(|code_point| { - let n = unsafe { char::from_u32_unchecked(code_point.value).encode_utf16() }; - let n = n.as_slice(); - if n.len() == 2 { - self.extra = n[1]; + let c = unsafe { + char::from_u32_unchecked(code_point.value) + }; + let n = c.encode_utf16(&mut buf).len(); + if n == 2 { + self.extra = buf[1]; } - n[0] + buf[0] }) } From ee0873ab366ccdf8a5bd7e712569cbddf34a7150 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sun, 2 Oct 2016 21:30:16 -0700 Subject: [PATCH 26/28] Make OpenOptions logic slightly less ugly --- src/services/fs.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index 6bca22a..c52addf 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -453,9 +453,7 @@ impl OpenOptions { /// /// If both truncate and append are set to true, the file will simply be truncated pub fn append(&mut self, append: bool) -> &mut OpenOptions { - // we're going to be cheeky and just manually set write access here self.append = append; - self.write = append; self } @@ -536,7 +534,7 @@ impl OpenOptions { } fn get_open_flags(&self) -> u32 { - match (self.read, self.write, self.create) { + 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, From 82bcf8f305c09f54e687b3517fbb9e694e6df296 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 3 Oct 2016 11:38:44 -0700 Subject: [PATCH 27/28] Fix documentation errors --- src/services/fs.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/services/fs.rs b/src/services/fs.rs index c52addf..d4aedc4 100644 --- a/src/services/fs.rs +++ b/src/services/fs.rs @@ -54,7 +54,7 @@ pub enum ArchiveID { /// Represents the filesystem service. No file IO can be performed /// until an instance of this struct is created. /// -/// The service exits when this struct goes out of scope. +/// The service exits when all instances of this struct go out of scope. pub struct Fs { pd: PhantomData, } @@ -208,8 +208,13 @@ impl Fs { /// # Errors /// /// This function will return Err if there was an error initializing the - /// FS service. This typically reflects a problem with the execution - /// environment and not necessarily your program itself. + /// 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 { unsafe { let r = fsInit(); @@ -264,11 +269,11 @@ impl File { /// # Examples /// /// ```no_run - /// use ctru::servies::fs::{Fs, File}; + /// use ctru::services::fs::{Fs, File}; /// /// let fs = Fs::init().unwrap() /// let sdmc_archive = fs.sdmc().unwrap() - /// let mut f = File::open("/foo.txt").unwrap(); + /// let mut f = File::open(&sdmc_archive, "/foo.txt").unwrap(); /// ``` pub fn open>(arch: &Archive, path: P) -> Result { OpenOptions::new().read(true).archive(arch).open(path.as_ref()) @@ -290,11 +295,11 @@ impl File { /// # Examples /// /// ```no_run - /// use ctru::servies::fs::{Fs, File}; + /// use ctru::services::fs::{Fs, File}; /// /// let fs = Fs::init().unwrap() /// let sdmc_archive = fs.sdmc().unwrap() - /// let mut f = File::create("/foo.txt").unwrap(); + /// let mut f = File::create(&sdmc_archive, "/foo.txt").unwrap(); /// ``` pub fn create>(arch: &Archive, path: P) -> Result { OpenOptions::new().write(true).create(true).archive(arch).open(path.as_ref()) From 20ff414230e3f93580ae32831f8f40e483037f50 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 3 Oct 2016 11:49:48 -0700 Subject: [PATCH 28/28] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 6eb5565..75bf43a 100644 --- a/README.md +++ b/README.md @@ -60,3 +60,7 @@ applies to every file in the tree, unless otherwise noted. must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. + +Rust is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0), with portions covered by various BSD-like licenses. + +See [LICENSE-APACHE](https://github.com/rust-lang/rust/blob/master/LICENSE-APACHE), [LICENSE-MIT](https://github.com/rust-lang/rust/blob/master/LICENSE-MIT), and [COPYRIGHT](https://github.com/rust-lang/rust/blob/master/COPYRIGHT) for details.