<commit_before>/*
 * Copyright (C) 2015 Benjamin Fry <benjaminfry@me.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
use byteorder::{ByteOrder, NetworkEndian};
use std::marker::PhantomData;

use error::{ProtoErrorKind, ProtoResult};

use super::BinEncodable;

/// Encode DNS messages and resource record types.
pub struct BinEncoder<'a> {
    offset: usize,
    buffer: &'a mut Vec<u8>,
    /// start and end of label pointers, smallvec here?
    name_pointers: Vec<(usize, usize)>,
    mode: EncodeMode,
    canonical_names: bool,
}

impl<'a> BinEncoder<'a> {
    /// Create a new encoder with the Vec to fill
    pub fn new(buf: &'a mut Vec<u8>) -> Self {
        Self::with_offset(buf, 0, EncodeMode::Normal)
    }

    /// Specify the mode for encoding
    ///
    /// # Arguments
    ///
    /// * `mode` - In Signing mode, it canonical forms of all data are encoded, otherwise format matches the source form
    pub fn with_mode(buf: &'a mut Vec<u8>, mode: EncodeMode) -> Self {
        Self::with_offset(buf, 0, mode)
    }

    /// Begins the encoder at the given offset
    ///
    /// This is used for pointers. If this encoder is starting at some point further in
    ///  the sequence of bytes, for the proper offset of the pointer, the offset accounts for that
    ///  by using the offset to add to the pointer location being written.
    ///
    /// # Arguments
    ///
    /// * `offset` - index at which to start writing into the buffer
    pub fn with_offset(buf: &'a mut Vec<u8>, offset: u32, mode: EncodeMode) -> Self {
        BinEncoder {
            offset: offset as usize,
            buffer: buf,
            name_pointers: Vec::new(),
            mode: mode,
            canonical_names: false,
        }
    }

    /// Returns a reference to the internal buffer
    pub fn into_bytes(self) -> &'a Vec<u8> {
        self.buffer
    }

    /// Returns the length of the buffer
    pub fn len(&self) -> usize {
        self.buffer.len()
    }

    /// Returns `true` if the buffer is empty
    pub fn is_empty(&self) -> bool {
        self.buffer.is_empty()
    }

    /// Returns the current offset into the buffer
    pub fn offset(&self) -> usize {
        self.offset
    }

    /// sets the current offset to the new offset
    pub fn set_offset(&mut self, offset: usize) {
        self.offset = offset;
    }

    /// Returns the current Encoding mode
    pub fn mode(&self) -> EncodeMode {
        self.mode
    }

    /// If set to true, then names will be written into the buffer in canonical form
    pub fn set_canonical_names(&mut self, canonical_names: bool) {
        self.canonical_names = canonical_names;
    }

    /// Returns true if then encoder is writing in canonical form
    pub fn is_canonical_names(&self) -> bool {
        self.canonical_names
    }

    /// Reserve specified length in the internal buffer.
    pub fn reserve(&mut self, len: usize) {
        if self.buffer.capacity() - self.buffer.len() < len {
            self.buffer.reserve(512.max(len));
        }
    }

    /// trims to the current offset
    pub fn trim(&mut self) {
        self.buffer.truncate(self.offset as usize);
    }

    /// Emit one byte into the buffer
    pub fn emit(&mut self, b: u8) -> ProtoResult<()> {
        if (self.offset as usize) < self.buffer.len() {
            *self.buffer
                .get_mut(self.offset as usize)
                .expect("this should always work!") = b;
        } else {
            self.buffer.push(b);
        }
        self.offset += 1;
        Ok(())
    }

    /// borrow a slice from the encoder
    pub fn slice_of(&self, start: usize, end: usize) -> &[u8] {
        assert!(start < self.offset);
        assert!(end <= self.buffer.len());
        &self.buffer[start..end]
    }

    /// Stores a label pointer to an already written label
    ///
    /// The location is the current position in the buffer
    ///  implicitly, it is expected that the name will be written to the stream after the current index.
    pub fn store_label_pointer(&mut self, start: usize, end: usize) {
        assert!(start <= (u16::max_value() as usize));
        assert!(end <= (u16::max_value() as usize));
        assert!(start <= end);
        if self.offset < 0x3FFF_usize {
            self.name_pointers.push((start, end)); // the next char will be at the len() location
        }
    }

    /// Looks up the index of an already written label
    pub fn get_label_pointer(&self, start: usize, end: usize) -> Option<u16> {
        let search = self.slice_of(start, end);

        for &(match_start, match_end) in &self.name_pointers {
            let matcher = self.slice_of(match_start as usize, match_end as usize);
            if matcher == search {
                assert!(match_start <= (u16::max_value() as usize));
                return Some(match_start as u16)
            }
        }

        None
    }

    /// matches description from above.
    ///
    /// ```
    /// use trust_dns_proto::serialize::binary::BinEncoder;
    ///
    /// let mut bytes: Vec<u8> = Vec::new();
    /// {
    ///   let mut encoder: BinEncoder = BinEncoder::new(&mut bytes);
    ///   encoder.emit_character_data("abc");
    /// }
    /// assert_eq!(bytes, vec![3,b'a',b'b',b'c']);
    /// ```
    pub fn emit_character_data(&mut self, char_data: &str) -> ProtoResult<()> {
        let char_bytes = char_data.as_bytes();
        if char_bytes.len() > 255 {
            return Err(ProtoErrorKind::CharacterDataTooLong(char_bytes.len()).into());
        }
        self.reserve(char_bytes.len() + 1); // reserve the full space for the string and length marker
        self.emit(char_bytes.len() as u8)?;
        self.write_slice(char_bytes);
        Ok(())
    }

    /// Emit one byte into the buffer
    pub fn emit_u8(&mut self, data: u8) -> ProtoResult<()> {
        self.emit(data)
    }

    /// Writes a u16 in network byte order to the buffer
    pub fn emit_u16(&mut self, data: u16) -> ProtoResult<()> {
        let mut bytes = [0; 2];
        {
            NetworkEndian::write_u16(&mut bytes, data);
        }
        self.write_slice(&bytes);
        Ok(())
    }

    /// Writes an i32 in network byte order to the buffer
    pub fn emit_i32(&mut self, data: i32) -> ProtoResult<()> {
        let mut bytes = [0; 4];
        {
            NetworkEndian::write_i32(&mut bytes, data);
        }
        self.write_slice(&bytes);
        Ok(())
    }

    /// Writes an u32 in network byte order to the buffer
    pub fn emit_u32(&mut self, data: u32) -> ProtoResult<()> {
        let mut bytes = [0; 4];
        {
            NetworkEndian::write_u32(&mut bytes, data);
        }
        self.write_slice(&bytes);
        Ok(())
    }

    fn write_slice(&mut self, data: &[u8]) {
        if self.offset == self.buffer.len() {
            self.buffer.extend_from_slice(data);
            self.offset += data.len();
        } else {
            let needed_len = self.offset + data.len();
            if needed_len < self.buffer.len() {
                self.buffer.resize(needed_len, 0);
            }

            for b in data {
                *self.buffer
                  .get_mut(self.offset)
                  .expect("this should always work!") = *b;
                self.offset += 1;
            }
        }
    }

    /// Writes the byte slice to the stream
    pub fn emit_vec(&mut self, data: &[u8]) -> ProtoResult<()> {
        self.write_slice(data);
        Ok(())
    }

    /// Emits all the elements of an Iterator to the encoder
    pub fn emit_all<'e, I: Iterator<Item = &'e E>, E: 'e + BinEncodable>(
        &mut self,
        iter: I,
    ) -> ProtoResult<()> {
        for i in iter {
            i.emit(self)?
        }
        Ok(())
    }

    /// Emits all the elements of an Iterator to the encoder
    pub fn emit_all_refs<'r, 'e, I, E>(&mut self, iter: I) -> ProtoResult<()>
    where
        'e: 'r,
        I: Iterator<Item = &'r &'e E>,
        E: 'r + 'e + BinEncodable,
    {
        for i in iter {
            i.emit(self)?
        }
        Ok(())
    }

    /// capture a location to write back to
    pub fn place<T: EncodedSize>(&mut self) -> Place<T> {
        let index = self.offset;
        let len = T::size_of();
        self.offset += len;
        self.buffer.reserve(len);
        for _ in 0..len {
            self.buffer.push(0_u8);
        }

        // update the offset
        self.offset += len;

        Place {
            start_index: index,
            phantom: PhantomData,
        }
    }

    /// calculates the length of data written since the place was creating
    pub fn len_since_place<T: EncodedSize>(&self, place: &Place<T>) -> usize {
        (self.offset - place.start_index) - place.size_of()
    }

    /// write back to a previously captured location
    pub fn emit_at<T: EncodedSize>(&mut self, place: Place<T>, data: T) -> ProtoResult<()> {
        // preserve current index
        let current_index = self.offset;

        // reset the current index back to place before writing
        //   this is an assert because it's programming error for it to be wrong.
        assert!(place.start_index < current_index);
        self.offset = place.start_index;

        // emit the data to be written at this place
        let emit_result = data.emit(self);

        // double check that the current number of bytes were written
        //   this is an assert because it's programming error for it to be wrong.
        assert!((self.offset - place.start_index) == place.size_of());

        // reset to original location
        self.offset = current_index;

        emit_result
    }
}

/// A trait to return the size of a type as it will be encoded in DNS
///
/// it does not necessarily equal `std::mem::size_of`, though it might, especially for primitives
pub trait EncodedSize: BinEncodable {
    fn size_of() -> usize;
}

impl EncodedSize for u16 {
    fn size_of() -> usize {
        2
    }
}

#[must_use = "data must be written back to the place"]
pub struct Place<T: EncodedSize> {
    start_index: usize,
    phantom: PhantomData<T>,
}

impl<T: EncodedSize> Place<T> {
    pub fn replace(self, encoder: &mut BinEncoder, data: T) -> ProtoResult<()> {
        encoder.emit_at(self, data)
    }

    pub fn size_of(&self) -> usize {
        T::size_of()
    }
}

/// In the Verify mode there maybe some things which are encoded differently, e.g. SIG0 records
///  should not be included in the additional count and not in the encoded data when in Verify
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum EncodeMode {
    /// In signing mode records are written in canonical form
    Signing,
    /// Write records in standard format
    Normal,
}

#[cfg(test)]
mod tests {
    use serialize::binary::BinDecoder;
    use super::*;

    #[test]
    fn test_size_of() {
        assert_eq!(u16::size_of(), 2);
    }

    #[test]
    fn test_place() {
        let mut buf = vec![];
        {
            let mut encoder = BinEncoder::new(&mut buf);
            let place = encoder.place::<u16>();
            assert_eq!(place.size_of(), 2);
            assert_eq!(encoder.len_since_place(&place), 0);

            encoder.emit(42_u8).expect("failed 0");
            assert_eq!(encoder.len_since_place(&place), 1);

            encoder.emit(48_u8).expect("failed 1");
            assert_eq!(encoder.len_since_place(&place), 2);

            place
                .replace(&mut encoder, 4_u16)
                .expect("failed to replace");
            drop(encoder);
        }

        assert_eq!(buf.len(), 4);

        let mut decoder = BinDecoder::new(&buf);
        let written = decoder.read_u16().expect("cound not read u16");

        assert_eq!(written, 4);
    }
}
<commit_msg>fix Place for new write_slice method<commit_after>/*
 * Copyright (C) 2015 Benjamin Fry <benjaminfry@me.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
use byteorder::{ByteOrder, NetworkEndian};
use std::marker::PhantomData;

use error::{ProtoErrorKind, ProtoResult};

use super::BinEncodable;

/// Encode DNS messages and resource record types.
pub struct BinEncoder<'a> {
    offset: usize,
    buffer: &'a mut Vec<u8>,
    /// start and end of label pointers, smallvec here?
    name_pointers: Vec<(usize, usize)>,
    mode: EncodeMode,
    canonical_names: bool,
}

impl<'a> BinEncoder<'a> {
    /// Create a new encoder with the Vec to fill
    pub fn new(buf: &'a mut Vec<u8>) -> Self {
        Self::with_offset(buf, 0, EncodeMode::Normal)
    }

    /// Specify the mode for encoding
    ///
    /// # Arguments
    ///
    /// * `mode` - In Signing mode, it canonical forms of all data are encoded, otherwise format matches the source form
    pub fn with_mode(buf: &'a mut Vec<u8>, mode: EncodeMode) -> Self {
        Self::with_offset(buf, 0, mode)
    }

    /// Begins the encoder at the given offset
    ///
    /// This is used for pointers. If this encoder is starting at some point further in
    ///  the sequence of bytes, for the proper offset of the pointer, the offset accounts for that
    ///  by using the offset to add to the pointer location being written.
    ///
    /// # Arguments
    ///
    /// * `offset` - index at which to start writing into the buffer
    pub fn with_offset(buf: &'a mut Vec<u8>, offset: u32, mode: EncodeMode) -> Self {
        BinEncoder {
            offset: offset as usize,
            buffer: buf,
            name_pointers: Vec::new(),
            mode: mode,
            canonical_names: false,
        }
    }

    /// Returns a reference to the internal buffer
    pub fn into_bytes(self) -> &'a Vec<u8> {
        self.buffer
    }

    /// Returns the length of the buffer
    pub fn len(&self) -> usize {
        self.buffer.len()
    }

    /// Returns `true` if the buffer is empty
    pub fn is_empty(&self) -> bool {
        self.buffer.is_empty()
    }

    /// Returns the current offset into the buffer
    pub fn offset(&self) -> usize {
        self.offset
    }

    /// sets the current offset to the new offset
    pub fn set_offset(&mut self, offset: usize) {
        self.offset = offset;
    }

    /// Returns the current Encoding mode
    pub fn mode(&self) -> EncodeMode {
        self.mode
    }

    /// If set to true, then names will be written into the buffer in canonical form
    pub fn set_canonical_names(&mut self, canonical_names: bool) {
        self.canonical_names = canonical_names;
    }

    /// Returns true if then encoder is writing in canonical form
    pub fn is_canonical_names(&self) -> bool {
        self.canonical_names
    }

    /// Reserve specified length in the internal buffer.
    pub fn reserve(&mut self, len: usize) {
        if self.buffer.capacity() - self.buffer.len() < len {
            self.buffer.reserve(512.max(len));
        }
    }

    /// trims to the current offset
    pub fn trim(&mut self) {
        self.buffer.truncate(self.offset as usize);
    }

    /// Emit one byte into the buffer
    pub fn emit(&mut self, b: u8) -> ProtoResult<()> {
        if self.offset < self.buffer.len() {
            *self.buffer
                .get_mut(self.offset)
                .expect("could not get index at offset") = b;
        } else {
            self.buffer.push(b);
        }
        self.offset += 1;
        Ok(())
    }

    /// borrow a slice from the encoder
    pub fn slice_of(&self, start: usize, end: usize) -> &[u8] {
        assert!(start < self.offset);
        assert!(end <= self.buffer.len());
        &self.buffer[start..end]
    }

    /// Stores a label pointer to an already written label
    ///
    /// The location is the current position in the buffer
    ///  implicitly, it is expected that the name will be written to the stream after the current index.
    pub fn store_label_pointer(&mut self, start: usize, end: usize) {
        assert!(start <= (u16::max_value() as usize));
        assert!(end <= (u16::max_value() as usize));
        assert!(start <= end);
        if self.offset < 0x3FFF_usize {
            self.name_pointers.push((start, end)); // the next char will be at the len() location
        }
    }

    /// Looks up the index of an already written label
    pub fn get_label_pointer(&self, start: usize, end: usize) -> Option<u16> {
        let search = self.slice_of(start, end);

        for &(match_start, match_end) in &self.name_pointers {
            let matcher = self.slice_of(match_start as usize, match_end as usize);
            if matcher == search {
                assert!(match_start <= (u16::max_value() as usize));
                return Some(match_start as u16)
            }
        }

        None
    }

    /// matches description from above.
    ///
    /// ```
    /// use trust_dns_proto::serialize::binary::BinEncoder;
    ///
    /// let mut bytes: Vec<u8> = Vec::new();
    /// {
    ///   let mut encoder: BinEncoder = BinEncoder::new(&mut bytes);
    ///   encoder.emit_character_data("abc");
    /// }
    /// assert_eq!(bytes, vec![3,b'a',b'b',b'c']);
    /// ```
    pub fn emit_character_data(&mut self, char_data: &str) -> ProtoResult<()> {
        let char_bytes = char_data.as_bytes();
        if char_bytes.len() > 255 {
            return Err(ProtoErrorKind::CharacterDataTooLong(char_bytes.len()).into());
        }
        self.reserve(char_bytes.len() + 1); // reserve the full space for the string and length marker
        self.emit(char_bytes.len() as u8)?;
        self.write_slice(char_bytes);
        Ok(())
    }

    /// Emit one byte into the buffer
    pub fn emit_u8(&mut self, data: u8) -> ProtoResult<()> {
        self.emit(data)
    }

    /// Writes a u16 in network byte order to the buffer
    pub fn emit_u16(&mut self, data: u16) -> ProtoResult<()> {
        let mut bytes = [0; 2];
        {
            NetworkEndian::write_u16(&mut bytes, data);
        }
        self.write_slice(&bytes);
        Ok(())
    }

    /// Writes an i32 in network byte order to the buffer
    pub fn emit_i32(&mut self, data: i32) -> ProtoResult<()> {
        let mut bytes = [0; 4];
        {
            NetworkEndian::write_i32(&mut bytes, data);
        }
        self.write_slice(&bytes);
        Ok(())
    }

    /// Writes an u32 in network byte order to the buffer
    pub fn emit_u32(&mut self, data: u32) -> ProtoResult<()> {
        let mut bytes = [0; 4];
        {
            NetworkEndian::write_u32(&mut bytes, data);
        }
        self.write_slice(&bytes);
        Ok(())
    }

    fn write_slice(&mut self, data: &[u8]) {
        // replacement case, the necessary space should have been reserved already...
        if self.offset < self.buffer.len() {
            for b in data {
                *self.buffer
                  .get_mut(self.offset)
                  .expect("could not get index at offset for slice") = *b;
                self.offset += 1;
            }
        } else {
            self.buffer.extend_from_slice(data);
            self.offset += data.len();
        }
    }

    /// Writes the byte slice to the stream
    pub fn emit_vec(&mut self, data: &[u8]) -> ProtoResult<()> {
        self.write_slice(data);
        Ok(())
    }

    /// Emits all the elements of an Iterator to the encoder
    pub fn emit_all<'e, I: Iterator<Item = &'e E>, E: 'e + BinEncodable>(
        &mut self,
        iter: I,
    ) -> ProtoResult<()> {
        for i in iter {
            i.emit(self)?
        }
        Ok(())
    }

    /// Emits all the elements of an Iterator to the encoder
    pub fn emit_all_refs<'r, 'e, I, E>(&mut self, iter: I) -> ProtoResult<()>
    where
        'e: 'r,
        I: Iterator<Item = &'r &'e E>,
        E: 'r + 'e + BinEncodable,
    {
        for i in iter {
            i.emit(self)?
        }
        Ok(())
    }

    /// capture a location to write back to
    pub fn place<T: EncodedSize>(&mut self) -> Place<T> {
        let index = self.offset;
        let len = T::size_of();

        // resize the buffer
        self.buffer.resize(index + len, 0);

        // update the offset
        self.offset += len;

        Place {
            start_index: index,
            phantom: PhantomData,
        }
    }

    /// calculates the length of data written since the place was creating
    pub fn len_since_place<T: EncodedSize>(&self, place: &Place<T>) -> usize {
        (self.offset - place.start_index) - place.size_of()
    }

    /// write back to a previously captured location
    pub fn emit_at<T: EncodedSize>(&mut self, place: Place<T>, data: T) -> ProtoResult<()> {
        // preserve current index
        let current_index = self.offset;

        // reset the current index back to place before writing
        //   this is an assert because it's programming error for it to be wrong.
        assert!(place.start_index < current_index);
        self.offset = place.start_index;

        // emit the data to be written at this place
        let emit_result = data.emit(self);

        // double check that the current number of bytes were written
        //   this is an assert because it's programming error for it to be wrong.
        assert!((self.offset - place.start_index) == place.size_of());

        // reset to original location
        self.offset = current_index;

        emit_result
    }
}

/// A trait to return the size of a type as it will be encoded in DNS
///
/// it does not necessarily equal `std::mem::size_of`, though it might, especially for primitives
pub trait EncodedSize: BinEncodable {
    fn size_of() -> usize;
}

impl EncodedSize for u16 {
    fn size_of() -> usize {
        2
    }
}

#[must_use = "data must be written back to the place"]
pub struct Place<T: EncodedSize> {
    start_index: usize,
    phantom: PhantomData<T>,
}

impl<T: EncodedSize> Place<T> {
    pub fn replace(self, encoder: &mut BinEncoder, data: T) -> ProtoResult<()> {
        encoder.emit_at(self, data)
    }

    pub fn size_of(&self) -> usize {
        T::size_of()
    }
}

/// In the Verify mode there maybe some things which are encoded differently, e.g. SIG0 records
///  should not be included in the additional count and not in the encoded data when in Verify
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum EncodeMode {
    /// In signing mode records are written in canonical form
    Signing,
    /// Write records in standard format
    Normal,
}

#[cfg(test)]
mod tests {
    use serialize::binary::BinDecoder;
    use super::*;

    #[test]
    fn test_size_of() {
        assert_eq!(u16::size_of(), 2);
    }

    #[test]
    fn test_place() {
        let mut buf = vec![];
        {
            let mut encoder = BinEncoder::new(&mut buf);
            let place = encoder.place::<u16>();
            assert_eq!(place.size_of(), 2);
            assert_eq!(encoder.len_since_place(&place), 0);

            encoder.emit(42_u8).expect("failed 0");
            assert_eq!(encoder.len_since_place(&place), 1);

            encoder.emit(48_u8).expect("failed 1");
            assert_eq!(encoder.len_since_place(&place), 2);

            place
                .replace(&mut encoder, 4_u16)
                .expect("failed to replace");
            drop(encoder);
        }

        assert_eq!(buf.len(), 4);

        let mut decoder = BinDecoder::new(&buf);
        let written = decoder.read_u16().expect("cound not read u16");

        assert_eq!(written, 4);
    }
}
<|endoftext|>


<commit_before>// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE file
// * that was distributed with this source code.

// spell-checker:ignore tcgetattr tcsetattr tcsanow

mod flags;

use clap::{crate_version, Arg, ArgMatches, Command};
use nix::sys::termios::{
    tcgetattr, tcsetattr, ControlFlags, InputFlags, LocalFlags, OutputFlags, Termios,
};
use std::io::{self, stdout};
use std::ops::ControlFlow;
use std::os::unix::io::{AsRawFd, RawFd};
use uucore::error::{UResult, USimpleError};
use uucore::{format_usage, InvalidEncodingHandling};

use flags::{CONTROL_FLAGS, INPUT_FLAGS, LOCAL_FLAGS, OUTPUT_FLAGS};

const NAME: &str = "stty";
const USAGE: &str = "\
{} [-F DEVICE | --file=DEVICE] [SETTING]...
{} [-F DEVICE | --file=DEVICE] [-a|--all]
{} [-F DEVICE | --file=DEVICE] [-g|--save]";
const SUMMARY: &str = "Print or change terminal characteristics.";

#[derive(Clone, Copy, Debug)]
pub struct Flag<T> {
    name: &'static str,
    flag: T,
    show: bool,
    sane: bool,
    group: Option<T>,
}

impl<T> Flag<T>
where
    T: Copy,
{
    pub const fn new(name: &'static str, flag: T) -> Self {
        Flag {
            name,
            flag,
            show: true,
            sane: false,
            group: None,
        }
    }

    pub const fn hidden(&self) -> Self {
        Self {
            show: false,
            ..*self
        }
    }

    pub const fn sane(&self) -> Self {
        Self {
            sane: true,
            ..*self
        }
    }

    pub const fn group(&self, group: T) -> Self {
        Self {
            group: Some(group),
            ..*self
        }
    }
}

trait TermiosFlag: Copy {
    fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool;
    fn apply(&self, termios: &mut Termios, val: bool);
}

mod options {
    pub const ALL: &str = "all";
    pub const SAVE: &str = "save";
    pub const FILE: &str = "file";
    pub const SETTINGS: &str = "settings";
}

struct Options<'a> {
    all: bool,
    _save: bool,
    file: RawFd,
    settings: Option<Vec<&'a str>>,
}

impl<'a> Options<'a> {
    fn from(matches: &'a ArgMatches) -> io::Result<Self> {
        Ok(Self {
            all: matches.is_present(options::ALL),
            _save: matches.is_present(options::SAVE),
            file: match matches.value_of(options::FILE) {
                Some(_f) => todo!(),
                None => stdout().as_raw_fd(),
            },
            settings: matches.values_of(options::SETTINGS).map(|v| v.collect()),
        })
    }
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    let args = args
        .collect_str(InvalidEncodingHandling::ConvertLossy)
        .accept_any();

    let matches = uu_app().get_matches_from(args);

    let opts = Options::from(&matches)?;

    stty(&opts)
}

fn stty(opts: &Options) -> UResult<()> {
    // TODO: Figure out the right error message
    let mut termios = tcgetattr(opts.file).expect("Could not get terminal attributes");
    if let Some(settings) = &opts.settings {
        for setting in settings {
            if let ControlFlow::Break(false) = apply_setting(&mut termios, setting) {
                return Err(USimpleError::new(
                    1,
                    format!("invalid argument '{}'", setting),
                ));
            }
        }

        tcsetattr(opts.file, nix::sys::termios::SetArg::TCSANOW, &termios)
            .expect("Could not write terminal attributes");
    } else {
        print_settings(&termios, opts);
    }
    Ok(())
}

fn print_settings(termios: &Termios, opts: &Options) {
    print_flags(termios, opts, &CONTROL_FLAGS);
    print_flags(termios, opts, &INPUT_FLAGS);
    print_flags(termios, opts, &OUTPUT_FLAGS);
    print_flags(termios, opts, &LOCAL_FLAGS);
}

fn print_flags<T: TermiosFlag>(termios: &Termios, opts: &Options, flags: &[Flag<T>]) {
    let mut printed = false;
    for &Flag {
        name,
        flag,
        show,
        sane,
        group,
    } in flags
    {
        if !show {
            continue;
        }
        let val = flag.is_in(termios, group);
        if group.is_some() {
            if val && (!sane || opts.all) {
                print!("{name} ");
                printed = true;
            }
        } else {
            if opts.all || val != sane {
                if !val {
                    print!("-");
                }
                print!("{name} ");
                printed = true;
            }
        }
    }
    if printed {
        println!();
    }
}

/// Apply a single setting
///
/// The value inside the `Break` variant of the `ControlFlow` indicates whether
/// the setting has been applied.
fn apply_setting(termios: &mut Termios, s: &str) -> ControlFlow<bool> {
    let (remove, name) = match s.strip_prefix('-') {
        Some(s) => (true, s),
        None => (false, s),
    };
    apply_flag(termios, &CONTROL_FLAGS, name, remove)?;
    apply_flag(termios, &INPUT_FLAGS, name, remove)?;
    apply_flag(termios, &OUTPUT_FLAGS, name, remove)?;
    apply_flag(termios, &LOCAL_FLAGS, name, remove)?;
    ControlFlow::Break(false)
}

/// Apply a flag to a slice of flags
///
/// The value inside the `Break` variant of the `ControlFlow` indicates whether
/// the setting has been applied.
fn apply_flag<T: TermiosFlag>(
    termios: &mut Termios,
    flags: &[Flag<T>],
    input: &str,
    remove: bool,
) -> ControlFlow<bool> {
    for Flag {
        name, flag, group, ..
    } in flags
    {
        if input == *name {
            // Flags with groups cannot be removed
            // Since the name matches, we can short circuit and don't have to check the other flags.
            if remove || group.is_some() {
                return ControlFlow::Break(false);
            }
            // If there is a group, the bits for that group should be cleared before applying the flag
            if let Some(group) = group {
                group.apply(termios, false);
            }
            flag.apply(termios, !remove);
            return ControlFlow::Break(true);
        }
    }
    ControlFlow::Continue(())
}

pub fn uu_app<'a>() -> Command<'a> {
    Command::new(uucore::util_name())
        .name(NAME)
        .version(crate_version!())
        .override_usage(format_usage(USAGE))
        .about(SUMMARY)
        .infer_long_args(true)
        .arg(Arg::new(options::ALL).short('a').long(options::ALL))
        .arg(Arg::new(options::SAVE).short('g').long(options::SAVE))
        .arg(
            Arg::new(options::FILE)
                .short('F')
                .long(options::FILE)
                .takes_value(true)
                .value_hint(clap::ValueHint::FilePath),
        )
        .arg(
            Arg::new(options::SETTINGS)
                .takes_value(true)
                .multiple_values(true),
        )
}

impl TermiosFlag for ControlFlags {
    fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool {
        termios.control_flags.contains(*self)
            && group.map_or(true, |g| !termios.control_flags.intersects(g - *self))
    }

    fn apply(&self, termios: &mut Termios, val: bool) {
        termios.control_flags.set(*self, val)
    }
}

impl TermiosFlag for InputFlags {
    fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool {
        termios.input_flags.contains(*self)
            && group.map_or(true, |g| !termios.input_flags.intersects(g - *self))
    }

    fn apply(&self, termios: &mut Termios, val: bool) {
        termios.input_flags.set(*self, val)
    }
}

impl TermiosFlag for OutputFlags {
    fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool {
        termios.output_flags.contains(*self)
            && group.map_or(true, |g| !termios.output_flags.intersects(g - *self))
    }

    fn apply(&self, termios: &mut Termios, val: bool) {
        termios.output_flags.set(*self, val)
    }
}

impl TermiosFlag for LocalFlags {
    fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool {
        termios.local_flags.contains(*self)
            && group.map_or(true, |g| !termios.local_flags.intersects(g - *self))
    }

    fn apply(&self, termios: &mut Termios, val: bool) {
        termios.local_flags.set(*self, val)
    }
}
<commit_msg>stty: fix clippy warnings<commit_after>// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE file
// * that was distributed with this source code.

// spell-checker:ignore tcgetattr tcsetattr tcsanow

mod flags;

use clap::{crate_version, Arg, ArgMatches, Command};
use nix::sys::termios::{
    tcgetattr, tcsetattr, ControlFlags, InputFlags, LocalFlags, OutputFlags, Termios,
};
use std::io::{self, stdout};
use std::ops::ControlFlow;
use std::os::unix::io::{AsRawFd, RawFd};
use uucore::error::{UResult, USimpleError};
use uucore::{format_usage, InvalidEncodingHandling};

use flags::{CONTROL_FLAGS, INPUT_FLAGS, LOCAL_FLAGS, OUTPUT_FLAGS};

const NAME: &str = "stty";
const USAGE: &str = "\
{} [-F DEVICE | --file=DEVICE] [SETTING]...
{} [-F DEVICE | --file=DEVICE] [-a|--all]
{} [-F DEVICE | --file=DEVICE] [-g|--save]";
const SUMMARY: &str = "Print or change terminal characteristics.";

#[derive(Clone, Copy, Debug)]
pub struct Flag<T> {
    name: &'static str,
    flag: T,
    show: bool,
    sane: bool,
    group: Option<T>,
}

impl<T> Flag<T>
where
    T: Copy,
{
    pub const fn new(name: &'static str, flag: T) -> Self {
        Self {
            name,
            flag,
            show: true,
            sane: false,
            group: None,
        }
    }

    pub const fn hidden(&self) -> Self {
        Self {
            show: false,
            ..*self
        }
    }

    pub const fn sane(&self) -> Self {
        Self {
            sane: true,
            ..*self
        }
    }

    pub const fn group(&self, group: T) -> Self {
        Self {
            group: Some(group),
            ..*self
        }
    }
}

trait TermiosFlag: Copy {
    fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool;
    fn apply(&self, termios: &mut Termios, val: bool);
}

mod options {
    pub const ALL: &str = "all";
    pub const SAVE: &str = "save";
    pub const FILE: &str = "file";
    pub const SETTINGS: &str = "settings";
}

struct Options<'a> {
    all: bool,
    _save: bool,
    file: RawFd,
    settings: Option<Vec<&'a str>>,
}

impl<'a> Options<'a> {
    fn from(matches: &'a ArgMatches) -> io::Result<Self> {
        Ok(Self {
            all: matches.is_present(options::ALL),
            _save: matches.is_present(options::SAVE),
            file: match matches.value_of(options::FILE) {
                Some(_f) => todo!(),
                None => stdout().as_raw_fd(),
            },
            settings: matches.values_of(options::SETTINGS).map(|v| v.collect()),
        })
    }
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    let args = args
        .collect_str(InvalidEncodingHandling::ConvertLossy)
        .accept_any();

    let matches = uu_app().get_matches_from(args);

    let opts = Options::from(&matches)?;

    stty(&opts)
}

fn stty(opts: &Options) -> UResult<()> {
    // TODO: Figure out the right error message
    let mut termios = tcgetattr(opts.file).expect("Could not get terminal attributes");
    if let Some(settings) = &opts.settings {
        for setting in settings {
            if let ControlFlow::Break(false) = apply_setting(&mut termios, setting) {
                return Err(USimpleError::new(
                    1,
                    format!("invalid argument '{}'", setting),
                ));
            }
        }

        tcsetattr(opts.file, nix::sys::termios::SetArg::TCSANOW, &termios)
            .expect("Could not write terminal attributes");
    } else {
        print_settings(&termios, opts);
    }
    Ok(())
}

fn print_settings(termios: &Termios, opts: &Options) {
    print_flags(termios, opts, &CONTROL_FLAGS);
    print_flags(termios, opts, &INPUT_FLAGS);
    print_flags(termios, opts, &OUTPUT_FLAGS);
    print_flags(termios, opts, &LOCAL_FLAGS);
}

fn print_flags<T: TermiosFlag>(termios: &Termios, opts: &Options, flags: &[Flag<T>]) {
    let mut printed = false;
    for &Flag {
        name,
        flag,
        show,
        sane,
        group,
    } in flags
    {
        if !show {
            continue;
        }
        let val = flag.is_in(termios, group);
        if group.is_some() {
            if val && (!sane || opts.all) {
                print!("{name} ");
                printed = true;
            }
        } else if opts.all || val != sane {
            if !val {
                print!("-");
            }
            print!("{name} ");
            printed = true;
        }
    }
    if printed {
        println!();
    }
}

/// Apply a single setting
///
/// The value inside the `Break` variant of the `ControlFlow` indicates whether
/// the setting has been applied.
fn apply_setting(termios: &mut Termios, s: &str) -> ControlFlow<bool> {
    let (remove, name) = match s.strip_prefix('-') {
        Some(s) => (true, s),
        None => (false, s),
    };
    apply_flag(termios, &CONTROL_FLAGS, name, remove)?;
    apply_flag(termios, &INPUT_FLAGS, name, remove)?;
    apply_flag(termios, &OUTPUT_FLAGS, name, remove)?;
    apply_flag(termios, &LOCAL_FLAGS, name, remove)?;
    ControlFlow::Break(false)
}

/// Apply a flag to a slice of flags
///
/// The value inside the `Break` variant of the `ControlFlow` indicates whether
/// the setting has been applied.
fn apply_flag<T: TermiosFlag>(
    termios: &mut Termios,
    flags: &[Flag<T>],
    input: &str,
    remove: bool,
) -> ControlFlow<bool> {
    for Flag {
        name, flag, group, ..
    } in flags
    {
        if input == *name {
            // Flags with groups cannot be removed
            // Since the name matches, we can short circuit and don't have to check the other flags.
            if remove || group.is_some() {
                return ControlFlow::Break(false);
            }
            // If there is a group, the bits for that group should be cleared before applying the flag
            if let Some(group) = group {
                group.apply(termios, false);
            }
            flag.apply(termios, !remove);
            return ControlFlow::Break(true);
        }
    }
    ControlFlow::Continue(())
}

pub fn uu_app<'a>() -> Command<'a> {
    Command::new(uucore::util_name())
        .name(NAME)
        .version(crate_version!())
        .override_usage(format_usage(USAGE))
        .about(SUMMARY)
        .infer_long_args(true)
        .arg(Arg::new(options::ALL).short('a').long(options::ALL))
        .arg(Arg::new(options::SAVE).short('g').long(options::SAVE))
        .arg(
            Arg::new(options::FILE)
                .short('F')
                .long(options::FILE)
                .takes_value(true)
                .value_hint(clap::ValueHint::FilePath),
        )
        .arg(
            Arg::new(options::SETTINGS)
                .takes_value(true)
                .multiple_values(true),
        )
}

impl TermiosFlag for ControlFlags {
    fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool {
        termios.control_flags.contains(*self)
            && group.map_or(true, |g| !termios.control_flags.intersects(g - *self))
    }

    fn apply(&self, termios: &mut Termios, val: bool) {
        termios.control_flags.set(*self, val);
    }
}

impl TermiosFlag for InputFlags {
    fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool {
        termios.input_flags.contains(*self)
            && group.map_or(true, |g| !termios.input_flags.intersects(g - *self))
    }

    fn apply(&self, termios: &mut Termios, val: bool) {
        termios.input_flags.set(*self, val);
    }
}

impl TermiosFlag for OutputFlags {
    fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool {
        termios.output_flags.contains(*self)
            && group.map_or(true, |g| !termios.output_flags.intersects(g - *self))
    }

    fn apply(&self, termios: &mut Termios, val: bool) {
        termios.output_flags.set(*self, val);
    }
}

impl TermiosFlag for LocalFlags {
    fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool {
        termios.local_flags.contains(*self)
            && group.map_or(true, |g| !termios.local_flags.intersects(g - *self))
    }

    fn apply(&self, termios: &mut Termios, val: bool) {
        termios.local_flags.set(*self, val);
    }
}
<|endoftext|>


<commit_before>// This file is part of the uutils coreutils package.
//
// (c) mahkoh (ju.orth [at] gmail [dot] com)
// (c) Daniel Rocco <drocco@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore (vars) egid euid FiletestOp StrlenOp

mod parser;

use clap::{crate_version, App, AppSettings};
use parser::{parse, Operator, Symbol, UnaryOperator};
use std::ffi::{OsStr, OsString};
use uucore::{display::Quotable, show_error};

const USAGE: &str = "test EXPRESSION
or:  test
or:  [ EXPRESSION ]
or:  [ ]
or:  [ OPTION";

// We use after_help so that this comes after the usage string (it would come before if we used about)
const AFTER_HELP: &str = "
Exit with the status determined by EXPRESSION.

An omitted EXPRESSION defaults to false.  Otherwise,
EXPRESSION is true or false and sets exit status.  It is one of:

  ( EXPRESSION )               EXPRESSION is true
  ! EXPRESSION                 EXPRESSION is false
  EXPRESSION1 -a EXPRESSION2   both EXPRESSION1 and EXPRESSION2 are true
  EXPRESSION1 -o EXPRESSION2   either EXPRESSION1 or EXPRESSION2 is true

  -n STRING            the length of STRING is nonzero
  STRING               equivalent to -n STRING
  -z STRING            the length of STRING is zero
  STRING1 = STRING2    the strings are equal
  STRING1 != STRING2   the strings are not equal

  INTEGER1 -eq INTEGER2   INTEGER1 is equal to INTEGER2
  INTEGER1 -ge INTEGER2   INTEGER1 is greater than or equal to INTEGER2
  INTEGER1 -gt INTEGER2   INTEGER1 is greater than INTEGER2
  INTEGER1 -le INTEGER2   INTEGER1 is less than or equal to INTEGER2
  INTEGER1 -lt INTEGER2   INTEGER1 is less than INTEGER2
  INTEGER1 -ne INTEGER2   INTEGER1 is not equal to INTEGER2

  FILE1 -ef FILE2   FILE1 and FILE2 have the same device and inode numbers
  FILE1 -nt FILE2   FILE1 is newer (modification date) than FILE2
  FILE1 -ot FILE2   FILE1 is older than FILE2

  -b FILE     FILE exists and is block special
  -c FILE     FILE exists and is character special
  -d FILE     FILE exists and is a directory
  -e FILE     FILE exists
  -f FILE     FILE exists and is a regular file
  -g FILE     FILE exists and is set-group-ID
  -G FILE     FILE exists and is owned by the effective group ID
  -h FILE     FILE exists and is a symbolic link (same as -L)
  -k FILE     FILE exists and has its sticky bit set
  -L FILE     FILE exists and is a symbolic link (same as -h)
  -N FILE     FILE exists and has been modified since it was last read
  -O FILE     FILE exists and is owned by the effective user ID
  -p FILE     FILE exists and is a named pipe
  -r FILE     FILE exists and read permission is granted
  -s FILE     FILE exists and has a size greater than zero
  -S FILE     FILE exists and is a socket
  -t FD       file descriptor FD is opened on a terminal
  -u FILE     FILE exists and its set-user-ID bit is set
  -w FILE     FILE exists and write permission is granted
  -x FILE     FILE exists and execute (or search) permission is granted

Except for -h and -L, all FILE-related tests dereference symbolic links.
Beware that parentheses need to be escaped (e.g., by backslashes) for shells.
INTEGER may also be -l STRING, which evaluates to the length of STRING.

NOTE: Binary -a and -o are inherently ambiguous.  Use 'test EXPR1 && test
EXPR2' or 'test EXPR1 || test EXPR2' instead.

NOTE: [ honors the --help and --version options, but test does not.
test treats each of those as it treats any other nonempty STRING.

NOTE: your shell may have its own version of test and/or [, which usually supersedes
the version described here.  Please refer to your shell's documentation
for details about the options it supports.";

pub fn uu_app() -> App<'static, 'static> {
    App::new(uucore::util_name())
        .setting(AppSettings::DisableHelpFlags)
        .setting(AppSettings::DisableVersion)
}

pub fn uumain(mut args: impl uucore::Args) -> i32 {
    let program = args.next().unwrap_or_else(|| OsString::from("test"));
    let binary_name = uucore::util_name();
    let mut args: Vec<_> = args.collect();

    if binary_name.ends_with('[') {
        // If invoked as [ we should recognize --help and --version (but not -h or -v)
        if args.len() == 1 && (args[0] == "--help" || args[0] == "--version") {
            // Let clap pretty-print help and version
            App::new(binary_name)
                .version(crate_version!())
                .usage(USAGE)
                .after_help(AFTER_HELP)
                // Disable printing of -h and -v as valid alternatives for --help and --version,
                // since we don't recognize -h and -v as help/version flags.
                .setting(AppSettings::NeedsLongHelp)
                .setting(AppSettings::NeedsLongVersion)
                .get_matches_from(std::iter::once(program).chain(args.into_iter()));
            return 0;
        }
        // If invoked via name '[', matching ']' must be in the last arg
        let last = args.pop();
        if last.as_deref() != Some(OsStr::new("]")) {
            show_error!("missing ']'");
            return 2;
        }
    }

    let result = parse(args).and_then(|mut stack| eval(&mut stack));

    match result {
        Ok(result) => {
            if result {
                0
            } else {
                1
            }
        }
        Err(e) => {
            show_error!("{}", e);
            2
        }
    }
}

/// Evaluate a stack of Symbols, returning the result of the evaluation or
/// an error message if evaluation failed.
fn eval(stack: &mut Vec<Symbol>) -> Result<bool, String> {
    macro_rules! pop_literal {
        () => {
            match stack.pop() {
                Some(Symbol::Literal(s)) => s,
                _ => panic!(),
            }
        };
    }

    let s = stack.pop();

    match s {
        Some(Symbol::Bang) => {
            let result = eval(stack)?;

            Ok(!result)
        }
        Some(Symbol::Op(Operator::String(op))) => {
            let b = stack.pop();
            let a = stack.pop();
            Ok(if op == "!=" { a != b } else { a == b })
        }
        Some(Symbol::Op(Operator::Int(op))) => {
            let b = pop_literal!();
            let a = pop_literal!();

            Ok(integers(&a, &b, &op)?)
        }
        Some(Symbol::Op(Operator::File(_op))) => unimplemented!(),
        Some(Symbol::UnaryOp(UnaryOperator::StrlenOp(op))) => {
            let s = match stack.pop() {
                Some(Symbol::Literal(s)) => s,
                Some(Symbol::None) => OsString::from(""),
                None => {
                    return Ok(true);
                }
                _ => {
                    return Err(format!("missing argument after '{:?}'", op));
                }
            };

            Ok(if op == "-z" {
                s.is_empty()
            } else {
                !s.is_empty()
            })
        }
        Some(Symbol::UnaryOp(UnaryOperator::FiletestOp(op))) => {
            let op = op.to_str().unwrap();

            let f = pop_literal!();

            Ok(match op {
                "-b" => path(&f, PathCondition::BlockSpecial),
                "-c" => path(&f, PathCondition::CharacterSpecial),
                "-d" => path(&f, PathCondition::Directory),
                "-e" => path(&f, PathCondition::Exists),
                "-f" => path(&f, PathCondition::Regular),
                "-g" => path(&f, PathCondition::GroupIdFlag),
                "-G" => path(&f, PathCondition::GroupOwns),
                "-h" => path(&f, PathCondition::SymLink),
                "-k" => path(&f, PathCondition::Sticky),
                "-L" => path(&f, PathCondition::SymLink),
                "-O" => path(&f, PathCondition::UserOwns),
                "-p" => path(&f, PathCondition::Fifo),
                "-r" => path(&f, PathCondition::Readable),
                "-S" => path(&f, PathCondition::Socket),
                "-s" => path(&f, PathCondition::NonEmpty),
                "-t" => isatty(&f)?,
                "-u" => path(&f, PathCondition::UserIdFlag),
                "-w" => path(&f, PathCondition::Writable),
                "-x" => path(&f, PathCondition::Executable),
                _ => panic!(),
            })
        }
        Some(Symbol::Literal(s)) => Ok(!s.is_empty()),
        Some(Symbol::None) => Ok(false),
        Some(Symbol::BoolOp(op)) => {
            let b = eval(stack)?;
            let a = eval(stack)?;

            Ok(if op == "-a" { a && b } else { a || b })
        }
        None => Ok(false),
        _ => Err("expected value".to_string()),
    }
}

fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> Result<bool, String> {
    let format_err = |value: &OsStr| format!("invalid integer {}", value.quote());

    let a: i64 = a
        .to_str()
        .and_then(|s| s.parse().ok())
        .ok_or_else(|| format_err(a))?;

    let b: i64 = b
        .to_str()
        .and_then(|s| s.parse().ok())
        .ok_or_else(|| format_err(b))?;

    Ok(match op.to_str() {
        Some("-eq") => a == b,
        Some("-ne") => a != b,
        Some("-gt") => a > b,
        Some("-ge") => a >= b,
        Some("-lt") => a < b,
        Some("-le") => a <= b,
        _ => return Err(format!("unknown operator {}", op.quote())),
    })
}

fn isatty(fd: &OsStr) -> Result<bool, String> {
    fd.to_str()
        .and_then(|s| s.parse().ok())
        .ok_or_else(|| format!("invalid integer {}", fd.quote()))
        .map(|i| {
            #[cfg(not(target_os = "redox"))]
            unsafe {
                libc::isatty(i) == 1
            }
            #[cfg(target_os = "redox")]
            syscall::dup(i, b"termios").map(syscall::close).is_ok()
        })
}

#[derive(Eq, PartialEq)]
enum PathCondition {
    BlockSpecial,
    CharacterSpecial,
    Directory,
    Exists,
    Regular,
    GroupIdFlag,
    GroupOwns,
    SymLink,
    Sticky,
    UserOwns,
    Fifo,
    Readable,
    Socket,
    NonEmpty,
    UserIdFlag,
    Writable,
    Executable,
}

#[cfg(not(windows))]
fn path(path: &OsStr, condition: PathCondition) -> bool {
    use std::fs::{self, Metadata};
    use std::os::unix::fs::{FileTypeExt, MetadataExt};

    const S_ISUID: u32 = 0o4000;
    const S_ISGID: u32 = 0o2000;
    const S_ISVTX: u32 = 0o1000;

    enum Permission {
        Read = 0o4,
        Write = 0o2,
        Execute = 0o1,
    }

    let geteuid = || {
        #[cfg(not(target_os = "redox"))]
        let euid = unsafe { libc::geteuid() };
        #[cfg(target_os = "redox")]
        let euid = syscall::geteuid().unwrap() as u32;

        euid
    };

    let getegid = || {
        #[cfg(not(target_os = "redox"))]
        let egid = unsafe { libc::getegid() };
        #[cfg(target_os = "redox")]
        let egid = syscall::getegid().unwrap() as u32;

        egid
    };

    let perm = |metadata: Metadata, p: Permission| {
        if geteuid() == metadata.uid() {
            metadata.mode() & ((p as u32) << 6) != 0
        } else if getegid() == metadata.gid() {
            metadata.mode() & ((p as u32) << 3) != 0
        } else {
            metadata.mode() & (p as u32) != 0
        }
    };

    let metadata = if condition == PathCondition::SymLink {
        fs::symlink_metadata(path)
    } else {
        fs::metadata(path)
    };

    let metadata = match metadata {
        Ok(metadata) => metadata,
        Err(_) => {
            return false;
        }
    };

    let file_type = metadata.file_type();

    match condition {
        PathCondition::BlockSpecial => file_type.is_block_device(),
        PathCondition::CharacterSpecial => file_type.is_char_device(),
        PathCondition::Directory => file_type.is_dir(),
        PathCondition::Exists => true,
        PathCondition::Regular => file_type.is_file(),
        PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0,
        PathCondition::GroupOwns => metadata.gid() == getegid(),
        PathCondition::SymLink => metadata.file_type().is_symlink(),
        PathCondition::Sticky => metadata.mode() & S_ISVTX != 0,
        PathCondition::UserOwns => metadata.uid() == geteuid(),
        PathCondition::Fifo => file_type.is_fifo(),
        PathCondition::Readable => perm(metadata, Permission::Read),
        PathCondition::Socket => file_type.is_socket(),
        PathCondition::NonEmpty => metadata.size() > 0,
        PathCondition::UserIdFlag => metadata.mode() & S_ISUID != 0,
        PathCondition::Writable => perm(metadata, Permission::Write),
        PathCondition::Executable => perm(metadata, Permission::Execute),
    }
}

#[cfg(windows)]
fn path(path: &OsStr, condition: PathCondition) -> bool {
    use std::fs::metadata;

    let stat = match metadata(path) {
        Ok(s) => s,
        _ => return false,
    };

    match condition {
        PathCondition::BlockSpecial => false,
        PathCondition::CharacterSpecial => false,
        PathCondition::Directory => stat.is_dir(),
        PathCondition::Exists => true,
        PathCondition::Regular => stat.is_file(),
        PathCondition::GroupIdFlag => false,
        PathCondition::GroupOwns => unimplemented!(),
        PathCondition::SymLink => false,
        PathCondition::Sticky => false,
        PathCondition::UserOwns => unimplemented!(),
        PathCondition::Fifo => false,
        PathCondition::Readable => false, // TODO
        PathCondition::Socket => false,
        PathCondition::NonEmpty => stat.len() > 0,
        PathCondition::UserIdFlag => false,
        PathCondition::Writable => false,   // TODO
        PathCondition::Executable => false, // TODO
    }
}
<commit_msg>test: return UResult from uumain() function<commit_after>// This file is part of the uutils coreutils package.
//
// (c) mahkoh (ju.orth [at] gmail [dot] com)
// (c) Daniel Rocco <drocco@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore (vars) egid euid FiletestOp StrlenOp

mod parser;

use clap::{crate_version, App, AppSettings};
use parser::{parse, Operator, Symbol, UnaryOperator};
use std::ffi::{OsStr, OsString};
use uucore::display::Quotable;
use uucore::error::{UResult, USimpleError};

const USAGE: &str = "test EXPRESSION
or:  test
or:  [ EXPRESSION ]
or:  [ ]
or:  [ OPTION";

// We use after_help so that this comes after the usage string (it would come before if we used about)
const AFTER_HELP: &str = "
Exit with the status determined by EXPRESSION.

An omitted EXPRESSION defaults to false.  Otherwise,
EXPRESSION is true or false and sets exit status.  It is one of:

  ( EXPRESSION )               EXPRESSION is true
  ! EXPRESSION                 EXPRESSION is false
  EXPRESSION1 -a EXPRESSION2   both EXPRESSION1 and EXPRESSION2 are true
  EXPRESSION1 -o EXPRESSION2   either EXPRESSION1 or EXPRESSION2 is true

  -n STRING            the length of STRING is nonzero
  STRING               equivalent to -n STRING
  -z STRING            the length of STRING is zero
  STRING1 = STRING2    the strings are equal
  STRING1 != STRING2   the strings are not equal

  INTEGER1 -eq INTEGER2   INTEGER1 is equal to INTEGER2
  INTEGER1 -ge INTEGER2   INTEGER1 is greater than or equal to INTEGER2
  INTEGER1 -gt INTEGER2   INTEGER1 is greater than INTEGER2
  INTEGER1 -le INTEGER2   INTEGER1 is less than or equal to INTEGER2
  INTEGER1 -lt INTEGER2   INTEGER1 is less than INTEGER2
  INTEGER1 -ne INTEGER2   INTEGER1 is not equal to INTEGER2

  FILE1 -ef FILE2   FILE1 and FILE2 have the same device and inode numbers
  FILE1 -nt FILE2   FILE1 is newer (modification date) than FILE2
  FILE1 -ot FILE2   FILE1 is older than FILE2

  -b FILE     FILE exists and is block special
  -c FILE     FILE exists and is character special
  -d FILE     FILE exists and is a directory
  -e FILE     FILE exists
  -f FILE     FILE exists and is a regular file
  -g FILE     FILE exists and is set-group-ID
  -G FILE     FILE exists and is owned by the effective group ID
  -h FILE     FILE exists and is a symbolic link (same as -L)
  -k FILE     FILE exists and has its sticky bit set
  -L FILE     FILE exists and is a symbolic link (same as -h)
  -N FILE     FILE exists and has been modified since it was last read
  -O FILE     FILE exists and is owned by the effective user ID
  -p FILE     FILE exists and is a named pipe
  -r FILE     FILE exists and read permission is granted
  -s FILE     FILE exists and has a size greater than zero
  -S FILE     FILE exists and is a socket
  -t FD       file descriptor FD is opened on a terminal
  -u FILE     FILE exists and its set-user-ID bit is set
  -w FILE     FILE exists and write permission is granted
  -x FILE     FILE exists and execute (or search) permission is granted

Except for -h and -L, all FILE-related tests dereference symbolic links.
Beware that parentheses need to be escaped (e.g., by backslashes) for shells.
INTEGER may also be -l STRING, which evaluates to the length of STRING.

NOTE: Binary -a and -o are inherently ambiguous.  Use 'test EXPR1 && test
EXPR2' or 'test EXPR1 || test EXPR2' instead.

NOTE: [ honors the --help and --version options, but test does not.
test treats each of those as it treats any other nonempty STRING.

NOTE: your shell may have its own version of test and/or [, which usually supersedes
the version described here.  Please refer to your shell's documentation
for details about the options it supports.";

pub fn uu_app() -> App<'static, 'static> {
    App::new(uucore::util_name())
        .setting(AppSettings::DisableHelpFlags)
        .setting(AppSettings::DisableVersion)
}

#[uucore_procs::gen_uumain]
pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
    let program = args.next().unwrap_or_else(|| OsString::from("test"));
    let binary_name = uucore::util_name();
    let mut args: Vec<_> = args.collect();

    if binary_name.ends_with('[') {
        // If invoked as [ we should recognize --help and --version (but not -h or -v)
        if args.len() == 1 && (args[0] == "--help" || args[0] == "--version") {
            // Let clap pretty-print help and version
            App::new(binary_name)
                .version(crate_version!())
                .usage(USAGE)
                .after_help(AFTER_HELP)
                // Disable printing of -h and -v as valid alternatives for --help and --version,
                // since we don't recognize -h and -v as help/version flags.
                .setting(AppSettings::NeedsLongHelp)
                .setting(AppSettings::NeedsLongVersion)
                .get_matches_from(std::iter::once(program).chain(args.into_iter()));
            return Ok(());
        }
        // If invoked via name '[', matching ']' must be in the last arg
        let last = args.pop();
        if last.as_deref() != Some(OsStr::new("]")) {
            return Err(USimpleError::new(2, "missing ']'"));
        }
    }

    let result = parse(args).and_then(|mut stack| eval(&mut stack));

    match result {
        Ok(result) => {
            if result {
                Ok(())
            } else {
                Err(1.into())
            }
        }
        Err(e) => Err(USimpleError::new(2, e)),
    }
}

/// Evaluate a stack of Symbols, returning the result of the evaluation or
/// an error message if evaluation failed.
fn eval(stack: &mut Vec<Symbol>) -> Result<bool, String> {
    macro_rules! pop_literal {
        () => {
            match stack.pop() {
                Some(Symbol::Literal(s)) => s,
                _ => panic!(),
            }
        };
    }

    let s = stack.pop();

    match s {
        Some(Symbol::Bang) => {
            let result = eval(stack)?;

            Ok(!result)
        }
        Some(Symbol::Op(Operator::String(op))) => {
            let b = stack.pop();
            let a = stack.pop();
            Ok(if op == "!=" { a != b } else { a == b })
        }
        Some(Symbol::Op(Operator::Int(op))) => {
            let b = pop_literal!();
            let a = pop_literal!();

            Ok(integers(&a, &b, &op)?)
        }
        Some(Symbol::Op(Operator::File(_op))) => unimplemented!(),
        Some(Symbol::UnaryOp(UnaryOperator::StrlenOp(op))) => {
            let s = match stack.pop() {
                Some(Symbol::Literal(s)) => s,
                Some(Symbol::None) => OsString::from(""),
                None => {
                    return Ok(true);
                }
                _ => {
                    return Err(format!("missing argument after '{:?}'", op));
                }
            };

            Ok(if op == "-z" {
                s.is_empty()
            } else {
                !s.is_empty()
            })
        }
        Some(Symbol::UnaryOp(UnaryOperator::FiletestOp(op))) => {
            let op = op.to_str().unwrap();

            let f = pop_literal!();

            Ok(match op {
                "-b" => path(&f, PathCondition::BlockSpecial),
                "-c" => path(&f, PathCondition::CharacterSpecial),
                "-d" => path(&f, PathCondition::Directory),
                "-e" => path(&f, PathCondition::Exists),
                "-f" => path(&f, PathCondition::Regular),
                "-g" => path(&f, PathCondition::GroupIdFlag),
                "-G" => path(&f, PathCondition::GroupOwns),
                "-h" => path(&f, PathCondition::SymLink),
                "-k" => path(&f, PathCondition::Sticky),
                "-L" => path(&f, PathCondition::SymLink),
                "-O" => path(&f, PathCondition::UserOwns),
                "-p" => path(&f, PathCondition::Fifo),
                "-r" => path(&f, PathCondition::Readable),
                "-S" => path(&f, PathCondition::Socket),
                "-s" => path(&f, PathCondition::NonEmpty),
                "-t" => isatty(&f)?,
                "-u" => path(&f, PathCondition::UserIdFlag),
                "-w" => path(&f, PathCondition::Writable),
                "-x" => path(&f, PathCondition::Executable),
                _ => panic!(),
            })
        }
        Some(Symbol::Literal(s)) => Ok(!s.is_empty()),
        Some(Symbol::None) => Ok(false),
        Some(Symbol::BoolOp(op)) => {
            let b = eval(stack)?;
            let a = eval(stack)?;

            Ok(if op == "-a" { a && b } else { a || b })
        }
        None => Ok(false),
        _ => Err("expected value".to_string()),
    }
}

fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> Result<bool, String> {
    let format_err = |value: &OsStr| format!("invalid integer {}", value.quote());

    let a: i64 = a
        .to_str()
        .and_then(|s| s.parse().ok())
        .ok_or_else(|| format_err(a))?;

    let b: i64 = b
        .to_str()
        .and_then(|s| s.parse().ok())
        .ok_or_else(|| format_err(b))?;

    Ok(match op.to_str() {
        Some("-eq") => a == b,
        Some("-ne") => a != b,
        Some("-gt") => a > b,
        Some("-ge") => a >= b,
        Some("-lt") => a < b,
        Some("-le") => a <= b,
        _ => return Err(format!("unknown operator {}", op.quote())),
    })
}

fn isatty(fd: &OsStr) -> Result<bool, String> {
    fd.to_str()
        .and_then(|s| s.parse().ok())
        .ok_or_else(|| format!("invalid integer {}", fd.quote()))
        .map(|i| {
            #[cfg(not(target_os = "redox"))]
            unsafe {
                libc::isatty(i) == 1
            }
            #[cfg(target_os = "redox")]
            syscall::dup(i, b"termios").map(syscall::close).is_ok()
        })
}

#[derive(Eq, PartialEq)]
enum PathCondition {
    BlockSpecial,
    CharacterSpecial,
    Directory,
    Exists,
    Regular,
    GroupIdFlag,
    GroupOwns,
    SymLink,
    Sticky,
    UserOwns,
    Fifo,
    Readable,
    Socket,
    NonEmpty,
    UserIdFlag,
    Writable,
    Executable,
}

#[cfg(not(windows))]
fn path(path: &OsStr, condition: PathCondition) -> bool {
    use std::fs::{self, Metadata};
    use std::os::unix::fs::{FileTypeExt, MetadataExt};

    const S_ISUID: u32 = 0o4000;
    const S_ISGID: u32 = 0o2000;
    const S_ISVTX: u32 = 0o1000;

    enum Permission {
        Read = 0o4,
        Write = 0o2,
        Execute = 0o1,
    }

    let geteuid = || {
        #[cfg(not(target_os = "redox"))]
        let euid = unsafe { libc::geteuid() };
        #[cfg(target_os = "redox")]
        let euid = syscall::geteuid().unwrap() as u32;

        euid
    };

    let getegid = || {
        #[cfg(not(target_os = "redox"))]
        let egid = unsafe { libc::getegid() };
        #[cfg(target_os = "redox")]
        let egid = syscall::getegid().unwrap() as u32;

        egid
    };

    let perm = |metadata: Metadata, p: Permission| {
        if geteuid() == metadata.uid() {
            metadata.mode() & ((p as u32) << 6) != 0
        } else if getegid() == metadata.gid() {
            metadata.mode() & ((p as u32) << 3) != 0
        } else {
            metadata.mode() & (p as u32) != 0
        }
    };

    let metadata = if condition == PathCondition::SymLink {
        fs::symlink_metadata(path)
    } else {
        fs::metadata(path)
    };

    let metadata = match metadata {
        Ok(metadata) => metadata,
        Err(_) => {
            return false;
        }
    };

    let file_type = metadata.file_type();

    match condition {
        PathCondition::BlockSpecial => file_type.is_block_device(),
        PathCondition::CharacterSpecial => file_type.is_char_device(),
        PathCondition::Directory => file_type.is_dir(),
        PathCondition::Exists => true,
        PathCondition::Regular => file_type.is_file(),
        PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0,
        PathCondition::GroupOwns => metadata.gid() == getegid(),
        PathCondition::SymLink => metadata.file_type().is_symlink(),
        PathCondition::Sticky => metadata.mode() & S_ISVTX != 0,
        PathCondition::UserOwns => metadata.uid() == geteuid(),
        PathCondition::Fifo => file_type.is_fifo(),
        PathCondition::Readable => perm(metadata, Permission::Read),
        PathCondition::Socket => file_type.is_socket(),
        PathCondition::NonEmpty => metadata.size() > 0,
        PathCondition::UserIdFlag => metadata.mode() & S_ISUID != 0,
        PathCondition::Writable => perm(metadata, Permission::Write),
        PathCondition::Executable => perm(metadata, Permission::Execute),
    }
}

#[cfg(windows)]
fn path(path: &OsStr, condition: PathCondition) -> bool {
    use std::fs::metadata;

    let stat = match metadata(path) {
        Ok(s) => s,
        _ => return false,
    };

    match condition {
        PathCondition::BlockSpecial => false,
        PathCondition::CharacterSpecial => false,
        PathCondition::Directory => stat.is_dir(),
        PathCondition::Exists => true,
        PathCondition::Regular => stat.is_file(),
        PathCondition::GroupIdFlag => false,
        PathCondition::GroupOwns => unimplemented!(),
        PathCondition::SymLink => false,
        PathCondition::Sticky => false,
        PathCondition::UserOwns => unimplemented!(),
        PathCondition::Fifo => false,
        PathCondition::Readable => false, // TODO
        PathCondition::Socket => false,
        PathCondition::NonEmpty => stat.len() > 0,
        PathCondition::UserIdFlag => false,
        PathCondition::Writable => false,   // TODO
        PathCondition::Executable => false, // TODO
    }
}
<|endoftext|>


<commit_before>//! Represents the state of providers in a message.

use crate::util::*;
use crate::{as_mut, as_ref, ffi_fn};
use anyhow::anyhow;
use libc::c_char;
use pact_matching::models::provider_states::ProviderState;
use serde_json::Value as JsonValue;

ffi_fn! {
    /// Get the name of the provider state as a string, which needs to be deleted with `string_delete`.
    fn provider_state_get_name(provider_state: *const ProviderState) -> *const c_char {
        let provider_state = as_ref!(provider_state);
        let name = string::to_c(&provider_state.name)?;
        name as *const c_char
    } {
        ptr::null_to::<c_char>()
    }
}

ffi_fn! {
    /// Get an iterator over the params of a provider state.
    ///
    /// This iterator carries a pointer to the provider state, and must
    /// not outlive the provider state.
    ///
    /// The provider state params also must not be modified during iteration. If it is,
    /// the old iterator must be deleted and a new iterator created.
    ///
    /// # Errors
    ///
    /// On failure, this function will return a NULL pointer.
    ///
    /// This function may fail if any of the Rust strings contain
    /// embedded null ('\0') bytes.
    fn provider_state_get_param_iter(
        provider_state: *mut ProviderState
    ) -> *mut ProviderStateParamIterator {
        let provider_state = as_mut!(provider_state);

        let iter = ProviderStateParamIterator {
            keys:  provider_state.params.keys().cloned().collect(),
            current: 0,
            provider_state: provider_state as *const ProviderState,
        };

        ptr::raw_to(iter)
    } {
        ptr::null_mut_to::<ProviderStateParamIterator>()
    }
}

ffi_fn! {
    /// Get the next key and value out of the iterator, if possible
    ///
    /// Returns a pointer to a heap allocated array of 2 elements, the pointer to the
    /// key string on the heap, and the pointer to the value string on the heap.
    ///
    /// The user needs to free both the contained strings and the array.
    fn provider_state_param_iter_next(
        iter: *mut ProviderStateParamIterator
    ) -> *mut ProviderStateParamPair {
        let iter = as_mut!(iter);
        let provider_state = as_ref!(iter.provider_state);
        let key = iter.next().ok_or(anyhow!("iter past the end of params"))?;
        let (key, value) = provider_state
            .params
            .get_key_value(key)
            .ok_or(anyhow!("iter provided invalid param key"))?;
        let pair = ProviderStateParamPair::new(key, value)?;
        ptr::raw_to(pair)
    } {
        ptr::null_mut_to::<ProviderStateParamPair>()
    }
}

ffi_fn! {
    /// Free the provider state param iterator when you're done using it.
    fn provider_state_param_iter_delete(iter: *mut ProviderStateParamIterator) {
        ptr::drop_raw(iter);
    }
}

ffi_fn! {
    /// Free a pair of key and value returned from `provider_state_param_iter_next`.
    fn provider_state_param_pair_delete(pair: *mut ProviderStateParamPair) {
        ptr::drop_raw(pair);
    }
}

/// An iterator that enables FFI iteration over provider state params by putting all the keys on the heap
/// and tracking which one we're currently at.
///
/// This assumes no mutation of the underlying provider state happens while the iterator is live.
#[derive(Debug)]
pub struct ProviderStateParamIterator {
    /// The provider state param keys
    keys: Vec<String>,
    /// The current key
    current: usize,
    /// Pointer to the provider state.
    provider_state: *const ProviderState,
}

impl ProviderStateParamIterator {
    fn next(&mut self) -> Option<&String> {
        let idx = self.current;
        self.current += 1;
        self.keys.get(idx)
    }
}

/// A single key-value pair exported to the C-side.
#[derive(Debug)]
#[repr(C)]
#[allow(missing_copy_implementations)]
pub struct ProviderStateParamPair {
    /// The key of the `ProviderState` parameter.
    key: *const c_char,
    /// The value of the `ProviderState` parameter.
    value: *const c_char,
}

impl ProviderStateParamPair {
    fn new(
        key: &str,
        value: &JsonValue,
    ) -> anyhow::Result<ProviderStateParamPair> {
        let value = value.to_string();

        Ok(ProviderStateParamPair {
            key: string::to_c(key)? as *const c_char,
            value: string::to_c(&value)? as *const c_char,
        })
    }
}
<commit_msg>Added provider_state_delete function (#60)<commit_after>//! Represents the state of providers in a message.

use crate::util::*;
use crate::{as_mut, as_ref, ffi_fn};
use anyhow::anyhow;
use libc::c_char;
use pact_matching::models::provider_states::ProviderState;
use serde_json::Value as JsonValue;

ffi_fn! {
    /// Get the name of the provider state as a string, which needs to be deleted with `string_delete`.
    fn provider_state_get_name(provider_state: *const ProviderState) -> *const c_char {
        let provider_state = as_ref!(provider_state);
        let name = string::to_c(&provider_state.name)?;
        name as *const c_char
    } {
        ptr::null_to::<c_char>()
    }
}

ffi_fn! {
    /// Get an iterator over the params of a provider state.
    ///
    /// This iterator carries a pointer to the provider state, and must
    /// not outlive the provider state.
    ///
    /// The provider state params also must not be modified during iteration. If it is,
    /// the old iterator must be deleted and a new iterator created.
    ///
    /// # Errors
    ///
    /// On failure, this function will return a NULL pointer.
    ///
    /// This function may fail if any of the Rust strings contain
    /// embedded null ('\0') bytes.
    fn provider_state_get_param_iter(
        provider_state: *mut ProviderState
    ) -> *mut ProviderStateParamIterator {
        let provider_state = as_mut!(provider_state);

        let iter = ProviderStateParamIterator {
            keys:  provider_state.params.keys().cloned().collect(),
            current: 0,
            provider_state: provider_state as *const ProviderState,
        };

        ptr::raw_to(iter)
    } {
        ptr::null_mut_to::<ProviderStateParamIterator>()
    }
}

ffi_fn! {
    /// Get the next key and value out of the iterator, if possible
    ///
    /// Returns a pointer to a heap allocated array of 2 elements, the pointer to the
    /// key string on the heap, and the pointer to the value string on the heap.
    ///
    /// The user needs to free both the contained strings and the array.
    fn provider_state_param_iter_next(
        iter: *mut ProviderStateParamIterator
    ) -> *mut ProviderStateParamPair {
        let iter = as_mut!(iter);
        let provider_state = as_ref!(iter.provider_state);
        let key = iter.next().ok_or(anyhow!("iter past the end of params"))?;
        let (key, value) = provider_state
            .params
            .get_key_value(key)
            .ok_or(anyhow!("iter provided invalid param key"))?;
        let pair = ProviderStateParamPair::new(key, value)?;
        ptr::raw_to(pair)
    } {
        ptr::null_mut_to::<ProviderStateParamPair>()
    }
}

ffi_fn! {
    /// Free the provider state when you're done using it.
    fn provider_state_delete(provider_state: *mut ProviderState) {
        ptr::drop_raw(provider_state);
    }
}

ffi_fn! {
    /// Free the provider state param iterator when you're done using it.
    fn provider_state_param_iter_delete(iter: *mut ProviderStateParamIterator) {
        ptr::drop_raw(iter);
    }
}

ffi_fn! {
    /// Free a pair of key and value returned from `provider_state_param_iter_next`.
    fn provider_state_param_pair_delete(pair: *mut ProviderStateParamPair) {
        ptr::drop_raw(pair);
    }
}

/// An iterator that enables FFI iteration over provider state params by putting all the keys on the heap
/// and tracking which one we're currently at.
///
/// This assumes no mutation of the underlying provider state happens while the iterator is live.
#[derive(Debug)]
pub struct ProviderStateParamIterator {
    /// The provider state param keys
    keys: Vec<String>,
    /// The current key
    current: usize,
    /// Pointer to the provider state.
    provider_state: *const ProviderState,
}

impl ProviderStateParamIterator {
    fn next(&mut self) -> Option<&String> {
        let idx = self.current;
        self.current += 1;
        self.keys.get(idx)
    }
}

/// A single key-value pair exported to the C-side.
#[derive(Debug)]
#[repr(C)]
#[allow(missing_copy_implementations)]
pub struct ProviderStateParamPair {
    /// The key of the `ProviderState` parameter.
    key: *const c_char,
    /// The value of the `ProviderState` parameter.
    value: *const c_char,
}

impl ProviderStateParamPair {
    fn new(
        key: &str,
        value: &JsonValue,
    ) -> anyhow::Result<ProviderStateParamPair> {
        let value = value.to_string();

        Ok(ProviderStateParamPair {
            key: string::to_c(key)? as *const c_char,
            value: string::to_c(&value)? as *const c_char,
        })
    }
}
<|endoftext|>