<commit_before>use std::path::PathBuf;
use std::collections::BTreeMap;

use quire::validate as V;
use libc::{uid_t, gid_t};

#[derive(RustcDecodable, Clone, PartialEq, Eq)]
pub struct SnapshotInfo {
    pub size: usize,
    pub owner_uid: Option<uid_t>,
    pub owner_gid: Option<gid_t>,
}

#[derive(RustcDecodable, Clone, PartialEq, Eq)]
pub struct PersistentInfo {
    pub name: String,
}

#[derive(RustcDecodable, Clone, PartialEq, Eq)]
pub enum Volume {
    Tmpfs(TmpfsInfo),
    BindRW(PathBuf),
    BindRO(PathBuf),
    Empty,
    VaggaBin,
    Snapshot(SnapshotInfo),
    Container(String),
    Persistent(PersistentInfo),
}

#[derive(RustcDecodable, Clone, PartialEq, Eq)]
pub struct Dir {
    pub mode: u32,
}

#[derive(RustcDecodable, Clone, PartialEq, Eq)]
pub struct TmpfsInfo {
    pub size: usize,
    pub mode: u32,
    pub subdirs: BTreeMap<PathBuf, Dir>,
}

pub fn volume_validator<'x>() -> V::Enum<'x> {
    V::Enum::new()
    .option("Tmpfs",  V::Structure::new()
        .member("size",  V::Numeric::new()
            .min(0).default(100*1024*1024))
        .member("mode",  V::Numeric::new()
            .min(0).max(0o1777).default(0o1777))
        .member("subdirs",
            V::Mapping::new(
                V::Directory::new().is_absolute(false),
                V::Structure::new()
                    .member("mode", V::Numeric::new()
                        .min(0).max(0o1777).default(0o755))
            )))
    .option("VaggaBin",  V::Nothing)
    .option("BindRW",  V::Scalar::new())
    .option("BindRO",  V::Scalar::new())
    .option("Empty",  V::Nothing)
    .option("Snapshot",  V::Structure::new()
        .member("size",  V::Numeric::new().min(0).default(100*1024*1024))
        .member("owner_uid", V::Numeric::new().min(0).optional())
        .member("owner_gid", V::Numeric::new().min(0).optional())
        )
    .option("Container",  V::Scalar::new())
    .option("Persistent",  V::Structure::new()
        .member("name",  V::Scalar::new())
        )
}
<commit_msg>Allow to define `Persistent` as a string not a mapping (#204)<commit_after>use std::path::PathBuf;
use std::collections::BTreeMap;

use quire::validate as V;
use quire::ast::{Ast, Tag};
use libc::{uid_t, gid_t};

#[derive(RustcDecodable, Clone, PartialEq, Eq)]
pub struct SnapshotInfo {
    pub size: usize,
    pub owner_uid: Option<uid_t>,
    pub owner_gid: Option<gid_t>,
}

#[derive(RustcDecodable, Clone, PartialEq, Eq)]
pub struct PersistentInfo {
    pub name: String,
}

#[derive(RustcDecodable, Clone, PartialEq, Eq)]
pub enum Volume {
    Tmpfs(TmpfsInfo),
    BindRW(PathBuf),
    BindRO(PathBuf),
    Empty,
    VaggaBin,
    Snapshot(SnapshotInfo),
    Container(String),
    Persistent(PersistentInfo),
}

#[derive(RustcDecodable, Clone, PartialEq, Eq)]
pub struct Dir {
    pub mode: u32,
}

#[derive(RustcDecodable, Clone, PartialEq, Eq)]
pub struct TmpfsInfo {
    pub size: usize,
    pub mode: u32,
    pub subdirs: BTreeMap<PathBuf, Dir>,
}

pub fn volume_validator<'x>() -> V::Enum<'x> {
    V::Enum::new()
    .option("Tmpfs",  V::Structure::new()
        .member("size",  V::Numeric::new()
            .min(0).default(100*1024*1024))
        .member("mode",  V::Numeric::new()
            .min(0).max(0o1777).default(0o1777))
        .member("subdirs",
            V::Mapping::new(
                V::Directory::new().is_absolute(false),
                V::Structure::new()
                    .member("mode", V::Numeric::new()
                        .min(0).max(0o1777).default(0o755))
            )))
    .option("VaggaBin",  V::Nothing)
    .option("BindRW",  V::Scalar::new())
    .option("BindRO",  V::Scalar::new())
    .option("Empty",  V::Nothing)
    .option("Snapshot",  V::Structure::new()
        .member("size",  V::Numeric::new().min(0).default(100*1024*1024))
        .member("owner_uid", V::Numeric::new().min(0).optional())
        .member("owner_gid", V::Numeric::new().min(0).optional())
        )
    .option("Container",  V::Scalar::new())
    .option("Persistent",  V::Structure::new()
        .member("name",  V::Scalar::new())
        .parser(persistent_volume_string))
}

fn persistent_volume_string(ast: Ast) -> BTreeMap<String, Ast> {
    match ast {
        Ast::Scalar(pos, _, style, value) => {
            let mut map = BTreeMap::new();
            map.insert("name".to_string(),
                Ast::Scalar(pos.clone(), Tag::NonSpecific, style, value));
            map
        },
        _ => unreachable!(),
    }
}
<|endoftext|>


<commit_before>use crate::decoders::basics::*;

#[derive(Debug, Copy, Clone)]
pub struct BitPumpLSB<'a> {
  buffer: &'a [u8],
  pos: usize,
  bits: u64,
  nbits: u32,
}

impl<'a> BitPumpLSB<'a> {
  pub fn new(src: &'a [u8]) -> BitPumpLSB {
    BitPumpLSB {
      buffer: src,
      pos: 0,
      bits: 0,
      nbits: 0,
    }
  }
}

#[derive(Debug, Copy, Clone)]
pub struct BitPumpMSB<'a> {
  buffer: &'a [u8],
  pos: usize,
  bits: u64,
  nbits: u32,
}

impl<'a> BitPumpMSB<'a> {
  pub fn new(src: &'a [u8]) -> BitPumpMSB {
    BitPumpMSB {
      buffer: src,
      pos: 0,
      bits: 0,
      nbits: 0,
    }
  }
}

#[derive(Debug, Copy, Clone)]
pub struct BitPumpMSB32<'a> {
  buffer: &'a [u8],
  pos: usize,
  bits: u64,
  nbits: u32,
}

impl<'a> BitPumpMSB32<'a> {
  pub fn new(src: &'a [u8]) -> BitPumpMSB32 {
    BitPumpMSB32 {
      buffer: src,
      pos: 0,
      bits: 0,
      nbits: 0,
    }
  }

  pub fn get_pos(&self) -> usize {
    self.pos - ((self.nbits >> 3) as usize)
  }
}

#[derive(Debug, Copy, Clone)]
pub struct BitPumpJPEG<'a> {
  buffer: &'a [u8],
  pos: usize,
  bits: u64,
  nbits: u32,
  finished: bool,
}

impl<'a> BitPumpJPEG<'a> {
  pub fn new(src: &'a [u8]) -> BitPumpJPEG {
    BitPumpJPEG {
      buffer: src,
      pos: 0,
      bits: 0,
      nbits: 0,
      finished: false,
    }
  }
}

pub trait BitPump {
  fn peek_bits(&mut self, num: u32) -> u32;
  fn consume_bits(&mut self, num: u32);

  fn get_bits(&mut self, num: u32) -> u32 {
    if num == 0 {
      return 0
    }

    let val = self.peek_bits(num);
    self.consume_bits(num);

    val
  }

  fn peek_ibits(&mut self, num: u32) -> i32 {
    self.peek_bits(num) as i32
  }

  fn get_ibits(&mut self, num: u32) -> i32 {
    self.get_bits(num) as i32
  }

  // Sign extend ibits
  fn get_ibits_sextended(&mut self, num: u32) -> i32 {
    let val = self.get_ibits(num);
    val.wrapping_shl(32 - num).wrapping_shr(32 - num)
  }
}

impl<'a> BitPump for BitPumpLSB<'a> {
  fn peek_bits(&mut self, num: u32) -> u32 {
    if num > self.nbits {
      let inbits: u64 = LEu32(self.buffer, self.pos) as u64;
      self.bits = ((inbits << 32) | (self.bits << (32-self.nbits))) >> (32-self.nbits);
      self.pos += 4;
      self.nbits += 32;
    }
    (self.bits & (0x0ffffffffu64 >> (32-num))) as u32
  }

  fn consume_bits(&mut self, num: u32) {
    self.nbits -= num;
    self.bits >>= num;
  }
}

impl<'a> BitPump for BitPumpMSB<'a> {
  fn peek_bits(&mut self, num: u32) -> u32 {
    if num > self.nbits {
      let inbits: u64 = BEu32(self.buffer, self.pos) as u64;
      self.bits = (self.bits << 32) | inbits;
      self.pos += 4;
      self.nbits += 32;
    }
    (self.bits >> (self.nbits-num)) as u32
  }

  fn consume_bits(&mut self, num: u32) {
    self.nbits -= num;
    self.bits &= (1 << self.nbits) - 1;
  }
}

impl<'a> BitPump for BitPumpMSB32<'a> {
  fn peek_bits(&mut self, num: u32) -> u32 {
    if num > self.nbits {
      let inbits: u64 = LEu32(self.buffer, self.pos) as u64;
      self.bits = (self.bits << 32) | inbits;
      self.pos += 4;
      self.nbits += 32;
    }
    (self.bits >> (self.nbits-num)) as u32
  }

  fn consume_bits(&mut self, num: u32) {
    self.nbits -= num;
    self.bits &= (1 << self.nbits) - 1;
  }
}

impl<'a> BitPump for BitPumpJPEG<'a> {
  fn peek_bits(&mut self, num: u32) -> u32 {
    if num > self.nbits && !self.finished {
      if self.pos < self.buffer.len()-4 &&
         self.buffer[self.pos+0] != 0xff &&
         self.buffer[self.pos+1] != 0xff &&
         self.buffer[self.pos+2] != 0xff &&
         self.buffer[self.pos+3] != 0xff {
        let inbits: u64 = BEu32(self.buffer, self.pos) as u64;
        self.bits = (self.bits << 32) | inbits;
        self.pos += 4;
        self.nbits += 32;
      } else {
        // Read 32 bits the hard way
        let mut read_bytes = 0;
        while read_bytes < 4 && !self.finished {
          let byte = {
            if self.pos >= self.buffer.len() {
              self.finished = true;
              0
            } else {
              let nextbyte = self.buffer[self.pos];
              if nextbyte != 0xff {
                nextbyte
              } else if self.buffer[self.pos+1] == 0x00 {
                self.pos += 1; // Skip the extra byte used to mark 255
                nextbyte
              } else {
                self.finished = true;
                0
              }
            }
          };
          self.bits = (self.bits << 8) | (byte as u64);
          self.pos += 1;
          self.nbits += 8;
          read_bytes += 1;
        }
      }
    }
    if num > self.nbits && self.finished {
      // Stuff with zeroes to not fail to read
      self.bits <<= 32;
      self.nbits += 32;
    }

    (self.bits >> (self.nbits-num)) as u32
  }

  fn consume_bits(&mut self, num: u32) {
    self.nbits -= num;
    self.bits &= (1 << self.nbits) - 1;
  }
}

#[derive(Debug, Copy, Clone)]
pub struct ByteStream<'a> {
  buffer: &'a [u8],
  pos: usize,
  endian: Endian,
}

impl<'a> ByteStream<'a> {
  pub fn new(src: &'a [u8], endian: Endian) -> ByteStream {
    ByteStream {
      buffer: src,
      pos: 0,
      endian: endian,
    }
  }

  pub fn get_pos(&self) -> usize { self.pos }

  pub fn peek_u8(&self) -> u8 { self.buffer[self.pos] }
  pub fn get_u8(&mut self) -> u8 {
    let val = self.peek_u8();
    self.pos += 1;
    val
  }

  pub fn peek_u16(&self) -> u16 { self.endian.ru16(self.buffer, self.pos) }
  pub fn get_u16(&mut self) -> u16 {
    let val = self.peek_u16();
    self.pos += 2;
    val
  }

//  pub fn peek_u32(&self) -> u32 { self.endian.ru32(self.buffer, self.pos) }
//  pub fn get_u32(&mut self) -> u32 {
//    let val = self.peek_u32();
//    self.pos += 4;
//    val
//  }

  pub fn consume_bytes(&mut self, num: usize) {
    self.pos += num
  }

  pub fn skip_to_marker(&mut self) -> Result<usize, String> {
    let mut skip_count = 0;
    while !(self.buffer[self.pos] == 0xFF &&
            self.buffer[self.pos+1] != 0 &&
            self.buffer[self.pos+1] != 0xFF) {
      self.pos += 1;
      skip_count += 1;
      if self.pos >= self.buffer.len() {
        return Err("No marker found inside rest of buffer".to_string())
      }
    }
    self.pos += 1; // Make the next byte the marker
    Ok(skip_count+1)
  }
}
<commit_msg>Force inlining of pump functions<commit_after>use crate::decoders::basics::*;

#[derive(Debug, Copy, Clone)]
pub struct BitPumpLSB<'a> {
  buffer: &'a [u8],
  pos: usize,
  bits: u64,
  nbits: u32,
}

impl<'a> BitPumpLSB<'a> {
  pub fn new(src: &'a [u8]) -> BitPumpLSB {
    BitPumpLSB {
      buffer: src,
      pos: 0,
      bits: 0,
      nbits: 0,
    }
  }
}

#[derive(Debug, Copy, Clone)]
pub struct BitPumpMSB<'a> {
  buffer: &'a [u8],
  pos: usize,
  bits: u64,
  nbits: u32,
}

impl<'a> BitPumpMSB<'a> {
  pub fn new(src: &'a [u8]) -> BitPumpMSB {
    BitPumpMSB {
      buffer: src,
      pos: 0,
      bits: 0,
      nbits: 0,
    }
  }
}

#[derive(Debug, Copy, Clone)]
pub struct BitPumpMSB32<'a> {
  buffer: &'a [u8],
  pos: usize,
  bits: u64,
  nbits: u32,
}

impl<'a> BitPumpMSB32<'a> {
  pub fn new(src: &'a [u8]) -> BitPumpMSB32 {
    BitPumpMSB32 {
      buffer: src,
      pos: 0,
      bits: 0,
      nbits: 0,
    }
  }

  #[inline(always)]
  pub fn get_pos(&self) -> usize {
    self.pos - ((self.nbits >> 3) as usize)
  }
}

#[derive(Debug, Copy, Clone)]
pub struct BitPumpJPEG<'a> {
  buffer: &'a [u8],
  pos: usize,
  bits: u64,
  nbits: u32,
  finished: bool,
}

impl<'a> BitPumpJPEG<'a> {
  pub fn new(src: &'a [u8]) -> BitPumpJPEG {
    BitPumpJPEG {
      buffer: src,
      pos: 0,
      bits: 0,
      nbits: 0,
      finished: false,
    }
  }
}

pub trait BitPump {
  fn peek_bits(&mut self, num: u32) -> u32;
  fn consume_bits(&mut self, num: u32);

  #[inline(always)]
  fn get_bits(&mut self, num: u32) -> u32 {
    if num == 0 {
      return 0
    }

    let val = self.peek_bits(num);
    self.consume_bits(num);

    val
  }

  #[inline(always)]
  fn peek_ibits(&mut self, num: u32) -> i32 {
    self.peek_bits(num) as i32
  }

  #[inline(always)]
  fn get_ibits(&mut self, num: u32) -> i32 {
    self.get_bits(num) as i32
  }

  // Sign extend ibits
  #[inline(always)]
  fn get_ibits_sextended(&mut self, num: u32) -> i32 {
    let val = self.get_ibits(num);
    val.wrapping_shl(32 - num).wrapping_shr(32 - num)
  }
}

impl<'a> BitPump for BitPumpLSB<'a> {
  #[inline(always)]
  fn peek_bits(&mut self, num: u32) -> u32 {
    if num > self.nbits {
      let inbits: u64 = LEu32(self.buffer, self.pos) as u64;
      self.bits = ((inbits << 32) | (self.bits << (32-self.nbits))) >> (32-self.nbits);
      self.pos += 4;
      self.nbits += 32;
    }
    (self.bits & (0x0ffffffffu64 >> (32-num))) as u32
  }

  #[inline(always)]
  fn consume_bits(&mut self, num: u32) {
    self.nbits -= num;
    self.bits >>= num;
  }
}

impl<'a> BitPump for BitPumpMSB<'a> {
  #[inline(always)]
  fn peek_bits(&mut self, num: u32) -> u32 {
    if num > self.nbits {
      let inbits: u64 = BEu32(self.buffer, self.pos) as u64;
      self.bits = (self.bits << 32) | inbits;
      self.pos += 4;
      self.nbits += 32;
    }
    (self.bits >> (self.nbits-num)) as u32
  }

  #[inline(always)]
  fn consume_bits(&mut self, num: u32) {
    self.nbits -= num;
    self.bits &= (1 << self.nbits) - 1;
  }
}

impl<'a> BitPump for BitPumpMSB32<'a> {
  #[inline(always)]
  fn peek_bits(&mut self, num: u32) -> u32 {
    if num > self.nbits {
      let inbits: u64 = LEu32(self.buffer, self.pos) as u64;
      self.bits = (self.bits << 32) | inbits;
      self.pos += 4;
      self.nbits += 32;
    }
    (self.bits >> (self.nbits-num)) as u32
  }

  #[inline(always)]
  fn consume_bits(&mut self, num: u32) {
    self.nbits -= num;
    self.bits &= (1 << self.nbits) - 1;
  }
}

impl<'a> BitPump for BitPumpJPEG<'a> {
  #[inline(always)]
  fn peek_bits(&mut self, num: u32) -> u32 {
    if num > self.nbits && !self.finished {
      if self.pos < self.buffer.len()-4 &&
         self.buffer[self.pos+0] != 0xff &&
         self.buffer[self.pos+1] != 0xff &&
         self.buffer[self.pos+2] != 0xff &&
         self.buffer[self.pos+3] != 0xff {
        let inbits: u64 = BEu32(self.buffer, self.pos) as u64;
        self.bits = (self.bits << 32) | inbits;
        self.pos += 4;
        self.nbits += 32;
      } else {
        // Read 32 bits the hard way
        let mut read_bytes = 0;
        while read_bytes < 4 && !self.finished {
          let byte = {
            if self.pos >= self.buffer.len() {
              self.finished = true;
              0
            } else {
              let nextbyte = self.buffer[self.pos];
              if nextbyte != 0xff {
                nextbyte
              } else if self.buffer[self.pos+1] == 0x00 {
                self.pos += 1; // Skip the extra byte used to mark 255
                nextbyte
              } else {
                self.finished = true;
                0
              }
            }
          };
          self.bits = (self.bits << 8) | (byte as u64);
          self.pos += 1;
          self.nbits += 8;
          read_bytes += 1;
        }
      }
    }
    if num > self.nbits && self.finished {
      // Stuff with zeroes to not fail to read
      self.bits <<= 32;
      self.nbits += 32;
    }

    (self.bits >> (self.nbits-num)) as u32
  }

  #[inline(always)]
  fn consume_bits(&mut self, num: u32) {
    self.nbits -= num;
    self.bits &= (1 << self.nbits) - 1;
  }
}

#[derive(Debug, Copy, Clone)]
pub struct ByteStream<'a> {
  buffer: &'a [u8],
  pos: usize,
  endian: Endian,
}

impl<'a> ByteStream<'a> {
  pub fn new(src: &'a [u8], endian: Endian) -> ByteStream {
    ByteStream {
      buffer: src,
      pos: 0,
      endian: endian,
    }
  }

  #[inline(always)]
  pub fn get_pos(&self) -> usize { self.pos }

  #[inline(always)]
  pub fn peek_u8(&self) -> u8 { self.buffer[self.pos] }
  #[inline(always)]
  pub fn get_u8(&mut self) -> u8 {
    let val = self.peek_u8();
    self.pos += 1;
    val
  }

  #[inline(always)]
  pub fn peek_u16(&self) -> u16 { self.endian.ru16(self.buffer, self.pos) }
  #[inline(always)]
  pub fn get_u16(&mut self) -> u16 {
    let val = self.peek_u16();
    self.pos += 2;
    val
  }

//  #[inline(always)]
//  pub fn peek_u32(&self) -> u32 { self.endian.ru32(self.buffer, self.pos) }
//  #[inline(always)]
//  pub fn get_u32(&mut self) -> u32 {
//    let val = self.peek_u32();
//    self.pos += 4;
//    val
//  }

  #[inline(always)]
  pub fn consume_bytes(&mut self, num: usize) {
    self.pos += num
  }

  #[inline(always)]
  pub fn skip_to_marker(&mut self) -> Result<usize, String> {
    let mut skip_count = 0;
    while !(self.buffer[self.pos] == 0xFF &&
            self.buffer[self.pos+1] != 0 &&
            self.buffer[self.pos+1] != 0xFF) {
      self.pos += 1;
      skip_count += 1;
      if self.pos >= self.buffer.len() {
        return Err("No marker found inside rest of buffer".to_string())
      }
    }
    self.pos += 1; // Make the next byte the marker
    Ok(skip_count+1)
  }
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file or at https://opensource.org/licenses/MIT.

//! A handler and associated types for the timeline action.

use std::path::PathBuf;
use std::result::Result;
use std::vec::Vec;

use sha2::{Digest, Sha256};
use rrg_macro::ack;
use rrg_proto::convert::FromLossy;

use crate::session::{self, Session};

/// A request type for the timeline action.
pub struct Request {
    root: PathBuf,
}

/// A response type for the timeline action.
struct Response {
    chunk_ids: Vec<ChunkId>,
}

/// An error type for failures that can occur during the timeline action.
#[derive(Debug)]
enum Error {
    /// A failure occurred during an attempt to start the recursive walk.
    WalkDir(std::io::Error),
    /// A failure occurred during encoding of the timeline entries.
    Encode(std::io::Error),
}

impl std::error::Error for Error {

    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        use Error::*;

        match *self {
            WalkDir(ref error) => Some(error),
            Encode(ref error) => Some(error),
        }
    }
}

impl std::fmt::Display for Error {

    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
        use Error::*;

        match *self {
            WalkDir(ref error) => {
                write!(fmt, "failed to start the recursive walk: {}", error)
            },
            Encode(ref error) => {
                write!(fmt, "failed to encode timeline entries: {}", error)
            },
        }
    }
}

impl From<Error> for session::Error {

    fn from(error: Error) -> session::Error {
        session::Error::action(error)
    }
}

/// A type representing unique identifier of a given chunk.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct ChunkId {
    /// A SHA-256 digest of the referenced chunk data.
    sha256: [u8; 32],
}

impl ChunkId {

    /// Creates a chunk identifier for the given chunk.
    fn of(chunk: &Chunk) -> ChunkId {
        ChunkId {
            sha256: Sha256::digest(&chunk.data).into(),
        }
    }

    /// Converts the chunk identifier into raw bytes of SHA-256 hash.
    fn to_sha256_bytes(self) -> Vec<u8> {
        self.sha256.to_vec()
    }
}

/// A type representing a particular chunk of the returned timeline.
struct Chunk {
    data: Vec<u8>,
}

impl Chunk {

    /// Constructs a chunk from the given blob of bytes.
    fn from_bytes(data: Vec<u8>) -> Chunk {
        Chunk {
            data: data,
        }
    }

    /// Returns an identifier of the chunk.
    fn id(&self) -> ChunkId {
        ChunkId::of(&self)
    }
}

impl FromLossy<crate::fs::Entry> for rrg_proto::TimelineEntry {

    fn from_lossy(entry: crate::fs::Entry) -> rrg_proto::TimelineEntry {
        use std::convert::TryFrom as _;
        #[cfg(target_family = "unix")]
        use std::os::unix::fs::MetadataExt as _;

        let atime_nanos = entry.metadata.accessed().ok().and_then(|atime| ack! {
            rrg_proto::nanos(atime),
            error: "failed to convert access time to seconds"
        });

        let mtime_nanos = entry.metadata.modified().ok().and_then(|mtime| ack! {
            rrg_proto::nanos(mtime),
            error: "failed to convert modification time to seconds"
        });

        let btime_nanos = entry.metadata.created().ok().and_then(|btime| ack! {
            rrg_proto::nanos(btime),
            error: "failed to convert creation time to seconds"
        });

        rrg_proto::TimelineEntry {
            path: Some(rrg_proto::path::to_bytes(entry.path)),
            #[cfg(target_family = "unix")]
            mode: Some(i64::from(entry.metadata.mode())),
            size: Some(entry.metadata.len()),
            #[cfg(target_family = "unix")]
            dev: i64::try_from(entry.metadata.dev()).ok(),
            #[cfg(target_family = "unix")]
            ino: Some(entry.metadata.ino()),
            #[cfg(target_family = "unix")]
            uid: i64::try_from(entry.metadata.uid()).ok(),
            #[cfg(target_family = "unix")]
            gid: i64::try_from(entry.metadata.gid()).ok(),
            atime_ns: atime_nanos.and_then(|nanos| i64::try_from(nanos).ok()),
            mtime_ns: mtime_nanos.and_then(|nanos| i64::try_from(nanos).ok()),
            #[cfg(target_family = "unix")]
            ctime_ns: Some(entry.metadata.ctime_nsec()),
            btime_ns: btime_nanos.and_then(|nanos| i64::try_from(nanos).ok()),
            // TODO: Export file attributes on Windows.
            ..rrg_proto::TimelineEntry::default()
        }
    }
}

/// Handles requests for the timeline action.
pub fn handle<S>(session: &mut S, request: Request) -> session::Result<()>
where
    S: Session,
{
    let entries = crate::fs::walk_dir(&request.root).map_err(Error::WalkDir)?
        .map(rrg_proto::TimelineEntry::from_lossy);

    let mut response = Response {
        chunk_ids: vec!(),
    };

    for part in crate::gzchunked::encode(entries) {
        let part = part.map_err(Error::Encode)?;

        let chunk = Chunk::from_bytes(part);
        let chunk_id = chunk.id();

        session.send(session::Sink::TRANSFER_STORE, chunk)?;
        response.chunk_ids.push(chunk_id);
    }

    session.reply(response)?;

    Ok(())
}

impl super::Request for Request {

    type Proto = rrg_proto::TimelineArgs;

    fn from_proto(proto: Self::Proto) -> Result<Request, session::ParseError> {
        let root_bytes = proto.root
            .ok_or(session::MissingFieldError::new("root"))?;

        Ok(Request {
            root: rrg_proto::path::from_bytes(root_bytes),
        })
    }
}

impl super::Response for Response {

    const RDF_NAME: Option<&'static str> = Some("TimelineResult");

    type Proto = rrg_proto::TimelineResult;

    fn into_proto(self) -> rrg_proto::TimelineResult {
        let chunk_ids = self.chunk_ids
            .into_iter()
            .map(ChunkId::to_sha256_bytes)
            .collect();

        rrg_proto::TimelineResult {
            entry_batch_blob_ids: chunk_ids,
        }
    }
}

impl super::Response for Chunk {

    const RDF_NAME: Option<&'static str> = Some("DataBlob");

    type Proto = rrg_proto::DataBlob;

    fn into_proto(self) -> rrg_proto::DataBlob {
        self.data.into()
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    use session::test::Fake as Session;

    #[test]
    fn test_non_existent_path() {
        let tempdir = tempfile::tempdir().unwrap();

        let request = Request {
            root: tempdir.path().join("foo")
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_err());
    }

    #[test]
    fn test_empty_dir() {
        let tempdir = tempfile::tempdir().unwrap();
        let tempdir_path = tempdir.path().to_path_buf();

        let request = Request {
            root: tempdir_path.clone(),
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_ok());

        let entries = entries(&session);
        assert_eq!(entries.len(), 1);
        assert_eq!(path(&entries[0]), Some(tempdir_path.clone()));
    }

    #[test]
    fn test_dir_with_files() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::File::create(tempdir.path().join("a")).unwrap();
        std::fs::File::create(tempdir.path().join("b")).unwrap();
        std::fs::File::create(tempdir.path().join("c")).unwrap();

        let request = Request {
            root: tempdir.path().to_path_buf(),
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_ok());

        let mut entries = entries(&session);
        entries.sort_by_key(|entry| entry.path.clone());

        assert_eq!(entries.len(), 4);
        assert_eq!(path(&entries[0]), Some(tempdir.path().to_path_buf()));
        assert_eq!(path(&entries[1]), Some(tempdir.path().join("a")));
        assert_eq!(path(&entries[2]), Some(tempdir.path().join("b")));
        assert_eq!(path(&entries[3]), Some(tempdir.path().join("c")));
    }

    #[test]
    fn test_dir_with_nested_dirs() {
        let tempdir = tempfile::tempdir().unwrap();
        let tempdir_path = tempdir.path().to_path_buf();

        std::fs::create_dir_all(tempdir_path.join("a").join("b")).unwrap();

        let request = Request {
            root: tempdir_path.clone(),
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_ok());

        let mut entries = entries(&session);
        entries.sort_by_key(|entry| entry.path.clone());

        assert_eq!(entries.len(), 3);
        assert_eq!(path(&entries[0]), Some(tempdir_path.clone()));
        assert_eq!(path(&entries[1]), Some(tempdir_path.join("a")));
        assert_eq!(path(&entries[2]), Some(tempdir_path.join("a").join("b")));
    }

    // Symlinking is supported only on Unix-like systems.
    #[cfg(target_family = "unix")]
    #[test]
    fn test_dir_with_circular_symlinks() {
        let tempdir = tempfile::tempdir().unwrap();

        let root_path = tempdir.path().to_path_buf();
        let dir_path = root_path.join("dir");
        let symlink_path = dir_path.join("symlink");

        std::fs::create_dir(&dir_path).unwrap();
        std::os::unix::fs::symlink(&dir_path, &symlink_path).unwrap();

        let request = Request {
            root: root_path.clone(),
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_ok());

        let mut entries = entries(&session);
        entries.sort_by_key(|entry| entry.path.clone());

        assert_eq!(entries.len(), 3);
        assert_eq!(path(&entries[0]), Some(root_path));
        assert_eq!(path(&entries[1]), Some(dir_path));
        assert_eq!(path(&entries[2]), Some(symlink_path));
    }

    #[test]
    fn test_dir_with_unicode_files() {
        let tempdir = tempfile::tempdir().unwrap();

        let root_path = tempdir.path().to_path_buf();
        let file_path_1 = root_path.join("zażółć gęślą jaźń");
        let file_path_2 = root_path.join("што й па мору");

        std::fs::File::create(&file_path_1).unwrap();
        std::fs::File::create(&file_path_2).unwrap();

        let request = Request {
            root: root_path.clone(),
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_ok());

        let mut entries = entries(&session);
        entries.sort_by_key(|entry| entry.path.clone());

        assert_eq!(entries.len(), 3);

        // macOS mangles Unicode-specific characters in filenames.
        #[cfg(not(target_os = "macos"))]
        {
            assert_eq!(path(&entries[0]), Some(root_path));
            assert_eq!(path(&entries[1]), Some(file_path_1));
            assert_eq!(path(&entries[2]), Some(file_path_2));
        }
    }

    #[test]
    fn test_file_metadata() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::write(tempdir.path().join("foo"), b"123456789").unwrap();

        let request = Request {
            root: tempdir.path().to_path_buf(),
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_ok());

        let mut entries = entries(&session);
        entries.sort_by_key(|entry| entry.path.clone());

        assert_eq!(entries.len(), 2);
        assert_eq!(path(&entries[1]), Some(tempdir.path().join("foo")));
        assert_eq!(entries[1].size, Some(9));

        // Information about the file mode, user and group identifiers is
        // available only on UNIX systems.
        #[cfg(target_family = "unix")]
        {
            let mode = entries[1].mode.unwrap() as libc::mode_t;
            assert_eq!(mode & libc::S_IFMT, libc::S_IFREG);

            assert_eq!(entries[1].uid, Some(users::get_current_uid() as i64));
            assert_eq!(entries[1].gid, Some(users::get_current_gid() as i64));
        }
    }

    #[test]
    fn test_hardlink_metadata() {
        let tempdir = tempfile::tempdir().unwrap();

        let root_path = tempdir.path().to_path_buf();
        let file_path = root_path.join("file");
        let hardlink_path = root_path.join("hardlink");

        std::fs::File::create(&file_path).unwrap();
        std::fs::hard_link(&file_path, &hardlink_path).unwrap();

        let request = Request {
            root: root_path.clone(),
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_ok());

        let mut entries = entries(&session);
        entries.sort_by_key(|entry| entry.path.clone());

        assert_eq!(entries.len(), 3);
        assert_eq!(path(&entries[1]), Some(file_path));
        assert_eq!(path(&entries[2]), Some(hardlink_path));

        // Information about inode is not available on Windows.
        #[cfg(not(target_os = "windows"))]
        assert_eq!(entries[1].ino, entries[2].ino);
    }

    /// Retrieves timeline entries from the given session object.
    fn entries(session: &Session) -> Vec<rrg_proto::TimelineEntry> {
        use std::collections::HashMap;
        use crate::session::Sink;

        let chunk_count = session.response_count(Sink::TRANSFER_STORE);
        assert_eq!(session.reply_count(), 1);
        assert_eq!(session.reply::<Response>(0).chunk_ids.len(), chunk_count);

        let chunks_by_id = session.responses::<Chunk>(Sink::TRANSFER_STORE)
            .map(|chunk| (chunk.id(), chunk))
            .collect::<HashMap<_, _>>();

        let chunks = session.reply::<Response>(0).chunk_ids
            .iter()
            .map(|chunk_id| &chunks_by_id[chunk_id].data[..]);

        crate::gzchunked::decode(chunks)
            .map(Result::unwrap)
            .collect()
    }

    /// Constructs a path for the given timeline entry.
    fn path(entry: &rrg_proto::TimelineEntry) -> Option<PathBuf> {
        entry.path.clone().map(rrg_proto::path::from_bytes)
    }
}
<commit_msg>Expose types of the timeline action.<commit_after>// Copyright 2020 Google LLC
//
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file or at https://opensource.org/licenses/MIT.

//! A handler and associated types for the timeline action.

use std::path::PathBuf;
use std::result::Result;
use std::vec::Vec;

use sha2::{Digest, Sha256};
use rrg_macro::ack;
use rrg_proto::convert::FromLossy;

use crate::session::{self, Session};

/// A request type for the timeline action.
pub struct Request {
    pub root: PathBuf,
}

/// A response type for the timeline action.
pub struct Response {
    pub chunk_ids: Vec<ChunkId>,
}

/// An error type for failures that can occur during the timeline action.
#[derive(Debug)]
enum Error {
    /// A failure occurred during an attempt to start the recursive walk.
    WalkDir(std::io::Error),
    /// A failure occurred during encoding of the timeline entries.
    Encode(std::io::Error),
}

impl std::error::Error for Error {

    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        use Error::*;

        match *self {
            WalkDir(ref error) => Some(error),
            Encode(ref error) => Some(error),
        }
    }
}

impl std::fmt::Display for Error {

    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
        use Error::*;

        match *self {
            WalkDir(ref error) => {
                write!(fmt, "failed to start the recursive walk: {}", error)
            },
            Encode(ref error) => {
                write!(fmt, "failed to encode timeline entries: {}", error)
            },
        }
    }
}

impl From<Error> for session::Error {

    fn from(error: Error) -> session::Error {
        session::Error::action(error)
    }
}

/// A type representing unique identifier of a given chunk.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ChunkId {
    /// A SHA-256 digest of the referenced chunk data.
    pub sha256: [u8; 32],
}

impl ChunkId {

    /// Creates a chunk identifier for the given chunk.
    fn of(chunk: &Chunk) -> ChunkId {
        ChunkId {
            sha256: Sha256::digest(&chunk.data).into(),
        }
    }

    /// Converts the chunk identifier into raw bytes of SHA-256 hash.
    fn to_sha256_bytes(self) -> Vec<u8> {
        self.sha256.to_vec()
    }
}

/// A type representing a particular chunk of the returned timeline.
pub struct Chunk {
    pub data: Vec<u8>,
}

impl Chunk {

    /// Constructs a chunk from the given blob of bytes.
    fn from_bytes(data: Vec<u8>) -> Chunk {
        Chunk {
            data: data,
        }
    }

    /// Returns an identifier of the chunk.
    fn id(&self) -> ChunkId {
        ChunkId::of(&self)
    }
}

impl FromLossy<crate::fs::Entry> for rrg_proto::TimelineEntry {

    fn from_lossy(entry: crate::fs::Entry) -> rrg_proto::TimelineEntry {
        use std::convert::TryFrom as _;
        #[cfg(target_family = "unix")]
        use std::os::unix::fs::MetadataExt as _;

        let atime_nanos = entry.metadata.accessed().ok().and_then(|atime| ack! {
            rrg_proto::nanos(atime),
            error: "failed to convert access time to seconds"
        });

        let mtime_nanos = entry.metadata.modified().ok().and_then(|mtime| ack! {
            rrg_proto::nanos(mtime),
            error: "failed to convert modification time to seconds"
        });

        let btime_nanos = entry.metadata.created().ok().and_then(|btime| ack! {
            rrg_proto::nanos(btime),
            error: "failed to convert creation time to seconds"
        });

        rrg_proto::TimelineEntry {
            path: Some(rrg_proto::path::to_bytes(entry.path)),
            #[cfg(target_family = "unix")]
            mode: Some(i64::from(entry.metadata.mode())),
            size: Some(entry.metadata.len()),
            #[cfg(target_family = "unix")]
            dev: i64::try_from(entry.metadata.dev()).ok(),
            #[cfg(target_family = "unix")]
            ino: Some(entry.metadata.ino()),
            #[cfg(target_family = "unix")]
            uid: i64::try_from(entry.metadata.uid()).ok(),
            #[cfg(target_family = "unix")]
            gid: i64::try_from(entry.metadata.gid()).ok(),
            atime_ns: atime_nanos.and_then(|nanos| i64::try_from(nanos).ok()),
            mtime_ns: mtime_nanos.and_then(|nanos| i64::try_from(nanos).ok()),
            #[cfg(target_family = "unix")]
            ctime_ns: Some(entry.metadata.ctime_nsec()),
            btime_ns: btime_nanos.and_then(|nanos| i64::try_from(nanos).ok()),
            // TODO: Export file attributes on Windows.
            ..rrg_proto::TimelineEntry::default()
        }
    }
}

/// Handles requests for the timeline action.
pub fn handle<S>(session: &mut S, request: Request) -> session::Result<()>
where
    S: Session,
{
    let entries = crate::fs::walk_dir(&request.root).map_err(Error::WalkDir)?
        .map(rrg_proto::TimelineEntry::from_lossy);

    let mut response = Response {
        chunk_ids: vec!(),
    };

    for part in crate::gzchunked::encode(entries) {
        let part = part.map_err(Error::Encode)?;

        let chunk = Chunk::from_bytes(part);
        let chunk_id = chunk.id();

        session.send(session::Sink::TRANSFER_STORE, chunk)?;
        response.chunk_ids.push(chunk_id);
    }

    session.reply(response)?;

    Ok(())
}

impl super::Request for Request {

    type Proto = rrg_proto::TimelineArgs;

    fn from_proto(proto: Self::Proto) -> Result<Request, session::ParseError> {
        let root_bytes = proto.root
            .ok_or(session::MissingFieldError::new("root"))?;

        Ok(Request {
            root: rrg_proto::path::from_bytes(root_bytes),
        })
    }
}

impl super::Response for Response {

    const RDF_NAME: Option<&'static str> = Some("TimelineResult");

    type Proto = rrg_proto::TimelineResult;

    fn into_proto(self) -> rrg_proto::TimelineResult {
        let chunk_ids = self.chunk_ids
            .into_iter()
            .map(ChunkId::to_sha256_bytes)
            .collect();

        rrg_proto::TimelineResult {
            entry_batch_blob_ids: chunk_ids,
        }
    }
}

impl super::Response for Chunk {

    const RDF_NAME: Option<&'static str> = Some("DataBlob");

    type Proto = rrg_proto::DataBlob;

    fn into_proto(self) -> rrg_proto::DataBlob {
        self.data.into()
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    use session::test::Fake as Session;

    #[test]
    fn test_non_existent_path() {
        let tempdir = tempfile::tempdir().unwrap();

        let request = Request {
            root: tempdir.path().join("foo")
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_err());
    }

    #[test]
    fn test_empty_dir() {
        let tempdir = tempfile::tempdir().unwrap();
        let tempdir_path = tempdir.path().to_path_buf();

        let request = Request {
            root: tempdir_path.clone(),
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_ok());

        let entries = entries(&session);
        assert_eq!(entries.len(), 1);
        assert_eq!(path(&entries[0]), Some(tempdir_path.clone()));
    }

    #[test]
    fn test_dir_with_files() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::File::create(tempdir.path().join("a")).unwrap();
        std::fs::File::create(tempdir.path().join("b")).unwrap();
        std::fs::File::create(tempdir.path().join("c")).unwrap();

        let request = Request {
            root: tempdir.path().to_path_buf(),
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_ok());

        let mut entries = entries(&session);
        entries.sort_by_key(|entry| entry.path.clone());

        assert_eq!(entries.len(), 4);
        assert_eq!(path(&entries[0]), Some(tempdir.path().to_path_buf()));
        assert_eq!(path(&entries[1]), Some(tempdir.path().join("a")));
        assert_eq!(path(&entries[2]), Some(tempdir.path().join("b")));
        assert_eq!(path(&entries[3]), Some(tempdir.path().join("c")));
    }

    #[test]
    fn test_dir_with_nested_dirs() {
        let tempdir = tempfile::tempdir().unwrap();
        let tempdir_path = tempdir.path().to_path_buf();

        std::fs::create_dir_all(tempdir_path.join("a").join("b")).unwrap();

        let request = Request {
            root: tempdir_path.clone(),
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_ok());

        let mut entries = entries(&session);
        entries.sort_by_key(|entry| entry.path.clone());

        assert_eq!(entries.len(), 3);
        assert_eq!(path(&entries[0]), Some(tempdir_path.clone()));
        assert_eq!(path(&entries[1]), Some(tempdir_path.join("a")));
        assert_eq!(path(&entries[2]), Some(tempdir_path.join("a").join("b")));
    }

    // Symlinking is supported only on Unix-like systems.
    #[cfg(target_family = "unix")]
    #[test]
    fn test_dir_with_circular_symlinks() {
        let tempdir = tempfile::tempdir().unwrap();

        let root_path = tempdir.path().to_path_buf();
        let dir_path = root_path.join("dir");
        let symlink_path = dir_path.join("symlink");

        std::fs::create_dir(&dir_path).unwrap();
        std::os::unix::fs::symlink(&dir_path, &symlink_path).unwrap();

        let request = Request {
            root: root_path.clone(),
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_ok());

        let mut entries = entries(&session);
        entries.sort_by_key(|entry| entry.path.clone());

        assert_eq!(entries.len(), 3);
        assert_eq!(path(&entries[0]), Some(root_path));
        assert_eq!(path(&entries[1]), Some(dir_path));
        assert_eq!(path(&entries[2]), Some(symlink_path));
    }

    #[test]
    fn test_dir_with_unicode_files() {
        let tempdir = tempfile::tempdir().unwrap();

        let root_path = tempdir.path().to_path_buf();
        let file_path_1 = root_path.join("zażółć gęślą jaźń");
        let file_path_2 = root_path.join("што й па мору");

        std::fs::File::create(&file_path_1).unwrap();
        std::fs::File::create(&file_path_2).unwrap();

        let request = Request {
            root: root_path.clone(),
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_ok());

        let mut entries = entries(&session);
        entries.sort_by_key(|entry| entry.path.clone());

        assert_eq!(entries.len(), 3);

        // macOS mangles Unicode-specific characters in filenames.
        #[cfg(not(target_os = "macos"))]
        {
            assert_eq!(path(&entries[0]), Some(root_path));
            assert_eq!(path(&entries[1]), Some(file_path_1));
            assert_eq!(path(&entries[2]), Some(file_path_2));
        }
    }

    #[test]
    fn test_file_metadata() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::write(tempdir.path().join("foo"), b"123456789").unwrap();

        let request = Request {
            root: tempdir.path().to_path_buf(),
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_ok());

        let mut entries = entries(&session);
        entries.sort_by_key(|entry| entry.path.clone());

        assert_eq!(entries.len(), 2);
        assert_eq!(path(&entries[1]), Some(tempdir.path().join("foo")));
        assert_eq!(entries[1].size, Some(9));

        // Information about the file mode, user and group identifiers is
        // available only on UNIX systems.
        #[cfg(target_family = "unix")]
        {
            let mode = entries[1].mode.unwrap() as libc::mode_t;
            assert_eq!(mode & libc::S_IFMT, libc::S_IFREG);

            assert_eq!(entries[1].uid, Some(users::get_current_uid() as i64));
            assert_eq!(entries[1].gid, Some(users::get_current_gid() as i64));
        }
    }

    #[test]
    fn test_hardlink_metadata() {
        let tempdir = tempfile::tempdir().unwrap();

        let root_path = tempdir.path().to_path_buf();
        let file_path = root_path.join("file");
        let hardlink_path = root_path.join("hardlink");

        std::fs::File::create(&file_path).unwrap();
        std::fs::hard_link(&file_path, &hardlink_path).unwrap();

        let request = Request {
            root: root_path.clone(),
        };

        let mut session = Session::new();
        assert!(handle(&mut session, request).is_ok());

        let mut entries = entries(&session);
        entries.sort_by_key(|entry| entry.path.clone());

        assert_eq!(entries.len(), 3);
        assert_eq!(path(&entries[1]), Some(file_path));
        assert_eq!(path(&entries[2]), Some(hardlink_path));

        // Information about inode is not available on Windows.
        #[cfg(not(target_os = "windows"))]
        assert_eq!(entries[1].ino, entries[2].ino);
    }

    /// Retrieves timeline entries from the given session object.
    fn entries(session: &Session) -> Vec<rrg_proto::TimelineEntry> {
        use std::collections::HashMap;
        use crate::session::Sink;

        let chunk_count = session.response_count(Sink::TRANSFER_STORE);
        assert_eq!(session.reply_count(), 1);
        assert_eq!(session.reply::<Response>(0).chunk_ids.len(), chunk_count);

        let chunks_by_id = session.responses::<Chunk>(Sink::TRANSFER_STORE)
            .map(|chunk| (chunk.id(), chunk))
            .collect::<HashMap<_, _>>();

        let chunks = session.reply::<Response>(0).chunk_ids
            .iter()
            .map(|chunk_id| &chunks_by_id[chunk_id].data[..]);

        crate::gzchunked::decode(chunks)
            .map(Result::unwrap)
            .collect()
    }

    /// Constructs a path for the given timeline entry.
    fn path(entry: &rrg_proto::TimelineEntry) -> Option<PathBuf> {
        entry.path.clone().map(rrg_proto::path::from_bytes)
    }
}
<|endoftext|>


<commit_before>use crate::{
    analysis::{
        function_parameters::{async_param_to_remove, CParameter},
        functions::{find_function, find_index_to_ignore, finish_function_name},
        imports::Imports,
        out_parameters::use_function_return_for_result,
        ref_mode::RefMode,
        rust_type::RustType,
    },
    config,
    consts::TYPE_PARAMETERS_START,
    env::Env,
    library::{
        Class, Concurrency, Function, Fundamental, Nullable, ParameterDirection, Type, TypeId,
    },
    traits::IntoString,
};
use std::{collections::vec_deque::VecDeque, slice::Iter};

#[derive(Clone, Eq, Debug, PartialEq)]
pub enum BoundType {
    NoWrapper,
    // lifetime
    IsA(Option<char>),
    // lifetime <- shouldn't be used but just in case...
    AsRef(Option<char>),
}

impl BoundType {
    pub fn need_isa(&self) -> bool {
        matches!(*self, Self::IsA(_))
    }

    // TODO: This is just a heuristic for now, based on what we do in codegen!
    // Theoretically the surrounding function should determine whether it needs to
    // reuse an alias (ie. to use in `call_func::<P, Q, R>`) or not.
    // In the latter case an `impl` is generated instead of a type name/alias.
    pub fn has_alias(&self) -> bool {
        matches!(*self, Self::NoWrapper)
    }
}

#[derive(Clone, Eq, Debug, PartialEq)]
pub struct Bound {
    pub bound_type: BoundType,
    pub parameter_name: String,
    /// Bound does not have an alias when `param: impl type_str` is used
    pub alias: Option<char>,
    pub type_str: String,
    pub callback_modified: bool,
}

#[derive(Clone, Debug)]
pub struct Bounds {
    unused: VecDeque<char>,
    used: Vec<Bound>,
    unused_lifetimes: VecDeque<char>,
    lifetimes: Vec<char>,
}

impl Default for Bounds {
    fn default() -> Bounds {
        Bounds {
            unused: (TYPE_PARAMETERS_START..)
                .take_while(|x| *x <= 'Z')
                .collect(),
            used: Vec::new(),
            unused_lifetimes: "abcdefg".chars().collect(),
            lifetimes: Vec::new(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct CallbackInfo {
    pub callback_type: String,
    pub success_parameters: String,
    pub error_parameters: String,
    pub bound_name: char,
}

impl Bounds {
    pub fn add_for_parameter(
        &mut self,
        env: &Env,
        func: &Function,
        par: &CParameter,
        r#async: bool,
        concurrency: Concurrency,
        configured_functions: &[&config::functions::Function],
    ) -> (Option<String>, Option<CallbackInfo>) {
        let type_name = RustType::builder(env, par.typ)
            .ref_mode(RefMode::ByRefFake)
            .try_build();
        if (r#async && async_param_to_remove(&par.name)) || type_name.is_err() {
            return (None, None);
        }
        let mut type_string = type_name.into_string();
        let mut callback_info = None;
        let mut ret = None;
        let mut need_is_into_check = false;

        if !par.instance_parameter && par.direction != ParameterDirection::Out {
            if let Some(bound_type) = Bounds::type_for(env, par.typ, par.nullable) {
                ret = Some(Bounds::get_to_glib_extra(&bound_type));
                if r#async && (par.name == "callback" || par.name.ends_with("_callback")) {
                    let func_name = func.c_identifier.as_ref().unwrap();
                    let finish_func_name = finish_function_name(func_name);
                    if let Some(function) = find_function(env, &finish_func_name) {
                        // FIXME: This should work completely based on the analysis of the finish() function
                        // but that a) happens afterwards and b) is not accessible from here either.
                        let mut out_parameters =
                            find_out_parameters(env, function, configured_functions);
                        if use_function_return_for_result(
                            env,
                            function.ret.typ,
                            &func.name,
                            configured_functions,
                        ) {
                            out_parameters.insert(
                                0,
                                RustType::builder(env, function.ret.typ)
                                    .direction(function.ret.direction)
                                    .nullable(function.ret.nullable)
                                    .try_build()
                                    .into_string(),
                            );
                        }
                        let parameters = format_out_parameters(&out_parameters);
                        let error_type = find_error_type(env, function);
                        type_string = format!(
                            "FnOnce(Result<{}, {}>) + Send + 'static",
                            parameters, error_type
                        );
                        let bound_name = *self.unused.front().unwrap();
                        callback_info = Some(CallbackInfo {
                            callback_type: type_string.clone(),
                            success_parameters: parameters,
                            error_parameters: error_type,
                            bound_name,
                        });
                    }
                } else if par.c_type == "GDestroyNotify" || env.library.type_(par.typ).is_function()
                {
                    need_is_into_check = par.c_type != "GDestroyNotify";
                    if let Type::Function(_) = env.library.type_(par.typ) {
                        let callback_parameters_config =
                            configured_functions.iter().find_map(|f| {
                                f.parameters
                                    .iter()
                                    .find(|p| p.ident.is_match(&par.name))
                                    .map(|p| &p.callback_parameters)
                            });

                        let mut rust_ty = RustType::builder(env, par.typ)
                            .direction(par.direction)
                            .scope(par.scope)
                            .concurrency(concurrency);
                        if let Some(callback_parameters_config) = callback_parameters_config {
                            rust_ty =
                                rust_ty.callback_parameters_config(callback_parameters_config);
                        }
                        type_string = rust_ty
                            .try_from_glib(&par.try_from_glib)
                            .try_build()
                            .into_string();
                        let bound_name = *self.unused.front().unwrap();
                        callback_info = Some(CallbackInfo {
                            callback_type: type_string.clone(),
                            success_parameters: String::new(),
                            error_parameters: String::new(),
                            bound_name,
                        });
                    }
                }
                if (!need_is_into_check || !*par.nullable) && par.c_type != "GDestroyNotify" {
                    self.add_parameter(&par.name, &type_string, bound_type, r#async)
                }
            }
        } else if par.instance_parameter {
            if let Some(bound_type) = Bounds::type_for(env, par.typ, par.nullable) {
                ret = Some(Bounds::get_to_glib_extra(&bound_type));
            }
        }

        (ret, callback_info)
    }

    pub fn type_for(env: &Env, type_id: TypeId, nullable: Nullable) -> Option<BoundType> {
        use self::BoundType::*;
        match env.library.type_(type_id) {
            Type::Fundamental(Fundamental::Filename | Fundamental::OsString) => Some(AsRef(None)),
            Type::Class(Class {
                final_type: true, ..
            }) => None,
            Type::Class(Class {
                final_type: false, ..
            }) => Some(IsA(None)),
            Type::Interface(..) => Some(IsA(None)),
            Type::List(_) | Type::SList(_) | Type::CArray(_) => None,
            Type::Fundamental(_) if *nullable => None,
            Type::Function(_) => Some(NoWrapper),
            _ => None,
        }
    }

    fn get_to_glib_extra(bound_type: &BoundType) -> String {
        use self::BoundType::*;
        match bound_type {
            AsRef(_) => ".as_ref()".to_owned(),
            IsA(_) => ".as_ref()".to_owned(),
            _ => String::new(),
        }
    }

    pub fn add_parameter(
        &mut self,
        name: &str,
        type_str: &str,
        mut bound_type: BoundType,
        r#async: bool,
    ) {
        if r#async && name == "callback" {
            bound_type = BoundType::NoWrapper;
        }
        if self.used.iter().any(|n| n.parameter_name == name) {
            return;
        }
        let alias = bound_type
            .has_alias()
            .then(|| self.unused.pop_front().expect("No free type aliases!"));
        self.used.push(Bound {
            bound_type,
            parameter_name: name.to_owned(),
            alias,
            type_str: type_str.to_owned(),
            callback_modified: false,
        });
    }

    pub fn get_parameter_bound(&self, name: &str) -> Option<&Bound> {
        self.iter().find(move |n| n.parameter_name == name)
    }

    pub fn update_imports(&self, imports: &mut Imports) {
        //TODO: import with versions
        use self::BoundType::*;
        for used in &self.used {
            match used.bound_type {
                NoWrapper => (),
                IsA(_) => imports.add("glib::object::IsA"),
                AsRef(_) => imports.add_used_type(&used.type_str),
            }
        }
    }

    pub fn is_empty(&self) -> bool {
        self.used.is_empty()
    }

    pub fn iter(&self) -> Iter<'_, Bound> {
        self.used.iter()
    }

    pub fn iter_lifetimes(&self) -> Iter<'_, char> {
        self.lifetimes.iter()
    }
}

#[derive(Clone, Debug)]
pub struct PropertyBound {
    pub alias: char,
    pub type_str: String,
}

impl PropertyBound {
    pub fn get(env: &Env, type_id: TypeId) -> Option<PropertyBound> {
        let type_ = env.type_(type_id);
        if type_.is_final_type() {
            return None;
        }
        Some(PropertyBound {
            alias: TYPE_PARAMETERS_START,
            type_str: RustType::builder(env, type_id)
                .ref_mode(RefMode::ByRefFake)
                .try_build()
                .into_string(),
        })
    }
}

fn find_out_parameters(
    env: &Env,
    function: &Function,
    configured_functions: &[&config::functions::Function],
) -> Vec<String> {
    let index_to_ignore = find_index_to_ignore(&function.parameters, Some(&function.ret));
    function
        .parameters
        .iter()
        .enumerate()
        .filter(|&(index, param)| {
            Some(index) != index_to_ignore
                && param.direction == ParameterDirection::Out
                && param.name != "error"
        })
        .map(|(_, param)| {
            // FIXME: This should work completely based on the analysis of the finish() function
            // but that a) happens afterwards and b) is not accessible from here either.
            let nullable = configured_functions
                .iter()
                .find_map(|f| {
                    f.parameters
                        .iter()
                        .filter(|p| p.ident.is_match(&param.name))
                        .find_map(|p| p.nullable)
                })
                .unwrap_or(param.nullable);

            RustType::builder(env, param.typ)
                .direction(param.direction)
                .nullable(nullable)
                .try_build()
                .into_string()
        })
        .collect()
}

fn format_out_parameters(parameters: &[String]) -> String {
    if parameters.len() == 1 {
        parameters[0].to_string()
    } else {
        format!("({})", parameters.join(", "))
    }
}

fn find_error_type(env: &Env, function: &Function) -> String {
    let error_param = function
        .parameters
        .iter()
        .find(|param| param.direction == ParameterDirection::Out && param.name == "error")
        .expect("error type");
    if let Type::Record(_) = env.type_(error_param.typ) {
        return RustType::builder(env, error_param.typ)
            .direction(error_param.direction)
            .try_build()
            .into_string();
    }
    panic!("cannot find error type")
}

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

    #[test]
    fn get_new_all() {
        let mut bounds: Bounds = Default::default();
        let typ = BoundType::IsA(None);
        bounds.add_parameter("a", "", typ.clone(), false);
        assert_eq!(bounds.iter().len(), 1);
        // Don't add second time
        bounds.add_parameter("a", "", typ.clone(), false);
        assert_eq!(bounds.iter().len(), 1);
        bounds.add_parameter("b", "", typ.clone(), false);
        bounds.add_parameter("c", "", typ.clone(), false);
        bounds.add_parameter("d", "", typ.clone(), false);
        bounds.add_parameter("e", "", typ.clone(), false);
        bounds.add_parameter("f", "", typ.clone(), false);
        bounds.add_parameter("g", "", typ.clone(), false);
        bounds.add_parameter("h", "", typ.clone(), false);
        assert_eq!(bounds.iter().len(), 8);
        bounds.add_parameter("h", "", typ.clone(), false);
        assert_eq!(bounds.iter().len(), 8);
        bounds.add_parameter("i", "", typ.clone(), false);
        bounds.add_parameter("j", "", typ.clone(), false);
        bounds.add_parameter("k", "", typ, false);
    }

    #[test]
    #[should_panic(expected = "No free type aliases!")]
    fn exhaust_type_parameters() {
        let mut bounds: Bounds = Default::default();
        let typ = BoundType::NoWrapper;
        for c in 'a'..='l' {
            // Should panic on `l` because all type parameters are exhausted
            bounds.add_parameter(c.to_string().as_str(), "", typ.clone(), false);
        }
    }

    #[test]
    fn get_parameter_bound() {
        let mut bounds: Bounds = Default::default();
        let typ = BoundType::NoWrapper;
        bounds.add_parameter("a", "", typ.clone(), false);
        bounds.add_parameter("b", "", typ.clone(), false);
        let bound = bounds.get_parameter_bound("a").unwrap();
        // `NoWrapper `bounds are expected to have an alias:
        assert_eq!(bound.alias, Some('P'));
        assert_eq!(bound.bound_type, typ);
        let bound = bounds.get_parameter_bound("b").unwrap();
        assert_eq!(bound.alias, Some('Q'));
        assert_eq!(bound.bound_type, typ);
        assert_eq!(bounds.get_parameter_bound("c"), None);
    }

    #[test]
    fn impl_bound() {
        let mut bounds: Bounds = Default::default();
        let typ = BoundType::IsA(None);
        bounds.add_parameter("a", "", typ.clone(), false);
        bounds.add_parameter("b", "", typ.clone(), false);
        let bound = bounds.get_parameter_bound("a").unwrap();
        // `IsA` is simplified to an inline `foo: impl IsA<Bar>` and
        // lacks an alias/type-parameter:
        assert_eq!(bound.alias, None);
        assert_eq!(bound.bound_type, typ);

        let typ = BoundType::AsRef(None);
        bounds.add_parameter("c", "", typ.clone(), false);
        let bound = bounds.get_parameter_bound("c").unwrap();
        // Same `impl AsRef<Foo>` simplification as `IsA`:
        assert_eq!(bound.alias, None);
        assert_eq!(bound.bound_type, typ);
    }
}
<commit_msg>Handle nullability configuration for async function return values<commit_after>use crate::{
    analysis::{
        function_parameters::{async_param_to_remove, CParameter},
        functions::{find_function, find_index_to_ignore, finish_function_name},
        imports::Imports,
        out_parameters::use_function_return_for_result,
        ref_mode::RefMode,
        rust_type::RustType,
    },
    config,
    consts::TYPE_PARAMETERS_START,
    env::Env,
    library::{
        Class, Concurrency, Function, Fundamental, Nullable, ParameterDirection, Type, TypeId,
    },
    traits::IntoString,
};
use std::{collections::vec_deque::VecDeque, slice::Iter};

#[derive(Clone, Eq, Debug, PartialEq)]
pub enum BoundType {
    NoWrapper,
    // lifetime
    IsA(Option<char>),
    // lifetime <- shouldn't be used but just in case...
    AsRef(Option<char>),
}

impl BoundType {
    pub fn need_isa(&self) -> bool {
        matches!(*self, Self::IsA(_))
    }

    // TODO: This is just a heuristic for now, based on what we do in codegen!
    // Theoretically the surrounding function should determine whether it needs to
    // reuse an alias (ie. to use in `call_func::<P, Q, R>`) or not.
    // In the latter case an `impl` is generated instead of a type name/alias.
    pub fn has_alias(&self) -> bool {
        matches!(*self, Self::NoWrapper)
    }
}

#[derive(Clone, Eq, Debug, PartialEq)]
pub struct Bound {
    pub bound_type: BoundType,
    pub parameter_name: String,
    /// Bound does not have an alias when `param: impl type_str` is used
    pub alias: Option<char>,
    pub type_str: String,
    pub callback_modified: bool,
}

#[derive(Clone, Debug)]
pub struct Bounds {
    unused: VecDeque<char>,
    used: Vec<Bound>,
    unused_lifetimes: VecDeque<char>,
    lifetimes: Vec<char>,
}

impl Default for Bounds {
    fn default() -> Bounds {
        Bounds {
            unused: (TYPE_PARAMETERS_START..)
                .take_while(|x| *x <= 'Z')
                .collect(),
            used: Vec::new(),
            unused_lifetimes: "abcdefg".chars().collect(),
            lifetimes: Vec::new(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct CallbackInfo {
    pub callback_type: String,
    pub success_parameters: String,
    pub error_parameters: String,
    pub bound_name: char,
}

impl Bounds {
    pub fn add_for_parameter(
        &mut self,
        env: &Env,
        func: &Function,
        par: &CParameter,
        r#async: bool,
        concurrency: Concurrency,
        configured_functions: &[&config::functions::Function],
    ) -> (Option<String>, Option<CallbackInfo>) {
        let type_name = RustType::builder(env, par.typ)
            .ref_mode(RefMode::ByRefFake)
            .try_build();
        if (r#async && async_param_to_remove(&par.name)) || type_name.is_err() {
            return (None, None);
        }
        let mut type_string = type_name.into_string();
        let mut callback_info = None;
        let mut ret = None;
        let mut need_is_into_check = false;

        if !par.instance_parameter && par.direction != ParameterDirection::Out {
            if let Some(bound_type) = Bounds::type_for(env, par.typ, par.nullable) {
                ret = Some(Bounds::get_to_glib_extra(&bound_type));
                if r#async && (par.name == "callback" || par.name.ends_with("_callback")) {
                    let func_name = func.c_identifier.as_ref().unwrap();
                    let finish_func_name = finish_function_name(func_name);
                    if let Some(function) = find_function(env, &finish_func_name) {
                        // FIXME: This should work completely based on the analysis of the finish() function
                        // but that a) happens afterwards and b) is not accessible from here either.
                        let mut out_parameters =
                            find_out_parameters(env, function, configured_functions);
                        if use_function_return_for_result(
                            env,
                            function.ret.typ,
                            &func.name,
                            configured_functions,
                        ) {
                            let nullable = configured_functions
                                .iter()
                                .find_map(|f| f.ret.nullable)
                                .unwrap_or(function.ret.nullable);

                            out_parameters.insert(
                                0,
                                RustType::builder(env, function.ret.typ)
                                    .direction(function.ret.direction)
                                    .nullable(nullable)
                                    .try_build()
                                    .into_string(),
                            );
                        }
                        let parameters = format_out_parameters(&out_parameters);
                        let error_type = find_error_type(env, function);
                        type_string = format!(
                            "FnOnce(Result<{}, {}>) + Send + 'static",
                            parameters, error_type
                        );
                        let bound_name = *self.unused.front().unwrap();
                        callback_info = Some(CallbackInfo {
                            callback_type: type_string.clone(),
                            success_parameters: parameters,
                            error_parameters: error_type,
                            bound_name,
                        });
                    }
                } else if par.c_type == "GDestroyNotify" || env.library.type_(par.typ).is_function()
                {
                    need_is_into_check = par.c_type != "GDestroyNotify";
                    if let Type::Function(_) = env.library.type_(par.typ) {
                        let callback_parameters_config =
                            configured_functions.iter().find_map(|f| {
                                f.parameters
                                    .iter()
                                    .find(|p| p.ident.is_match(&par.name))
                                    .map(|p| &p.callback_parameters)
                            });

                        let mut rust_ty = RustType::builder(env, par.typ)
                            .direction(par.direction)
                            .scope(par.scope)
                            .concurrency(concurrency);
                        if let Some(callback_parameters_config) = callback_parameters_config {
                            rust_ty =
                                rust_ty.callback_parameters_config(callback_parameters_config);
                        }
                        type_string = rust_ty
                            .try_from_glib(&par.try_from_glib)
                            .try_build()
                            .into_string();
                        let bound_name = *self.unused.front().unwrap();
                        callback_info = Some(CallbackInfo {
                            callback_type: type_string.clone(),
                            success_parameters: String::new(),
                            error_parameters: String::new(),
                            bound_name,
                        });
                    }
                }
                if (!need_is_into_check || !*par.nullable) && par.c_type != "GDestroyNotify" {
                    self.add_parameter(&par.name, &type_string, bound_type, r#async)
                }
            }
        } else if par.instance_parameter {
            if let Some(bound_type) = Bounds::type_for(env, par.typ, par.nullable) {
                ret = Some(Bounds::get_to_glib_extra(&bound_type));
            }
        }

        (ret, callback_info)
    }

    pub fn type_for(env: &Env, type_id: TypeId, nullable: Nullable) -> Option<BoundType> {
        use self::BoundType::*;
        match env.library.type_(type_id) {
            Type::Fundamental(Fundamental::Filename | Fundamental::OsString) => Some(AsRef(None)),
            Type::Class(Class {
                final_type: true, ..
            }) => None,
            Type::Class(Class {
                final_type: false, ..
            }) => Some(IsA(None)),
            Type::Interface(..) => Some(IsA(None)),
            Type::List(_) | Type::SList(_) | Type::CArray(_) => None,
            Type::Fundamental(_) if *nullable => None,
            Type::Function(_) => Some(NoWrapper),
            _ => None,
        }
    }

    fn get_to_glib_extra(bound_type: &BoundType) -> String {
        use self::BoundType::*;
        match bound_type {
            AsRef(_) => ".as_ref()".to_owned(),
            IsA(_) => ".as_ref()".to_owned(),
            _ => String::new(),
        }
    }

    pub fn add_parameter(
        &mut self,
        name: &str,
        type_str: &str,
        mut bound_type: BoundType,
        r#async: bool,
    ) {
        if r#async && name == "callback" {
            bound_type = BoundType::NoWrapper;
        }
        if self.used.iter().any(|n| n.parameter_name == name) {
            return;
        }
        let alias = bound_type
            .has_alias()
            .then(|| self.unused.pop_front().expect("No free type aliases!"));
        self.used.push(Bound {
            bound_type,
            parameter_name: name.to_owned(),
            alias,
            type_str: type_str.to_owned(),
            callback_modified: false,
        });
    }

    pub fn get_parameter_bound(&self, name: &str) -> Option<&Bound> {
        self.iter().find(move |n| n.parameter_name == name)
    }

    pub fn update_imports(&self, imports: &mut Imports) {
        //TODO: import with versions
        use self::BoundType::*;
        for used in &self.used {
            match used.bound_type {
                NoWrapper => (),
                IsA(_) => imports.add("glib::object::IsA"),
                AsRef(_) => imports.add_used_type(&used.type_str),
            }
        }
    }

    pub fn is_empty(&self) -> bool {
        self.used.is_empty()
    }

    pub fn iter(&self) -> Iter<'_, Bound> {
        self.used.iter()
    }

    pub fn iter_lifetimes(&self) -> Iter<'_, char> {
        self.lifetimes.iter()
    }
}

#[derive(Clone, Debug)]
pub struct PropertyBound {
    pub alias: char,
    pub type_str: String,
}

impl PropertyBound {
    pub fn get(env: &Env, type_id: TypeId) -> Option<PropertyBound> {
        let type_ = env.type_(type_id);
        if type_.is_final_type() {
            return None;
        }
        Some(PropertyBound {
            alias: TYPE_PARAMETERS_START,
            type_str: RustType::builder(env, type_id)
                .ref_mode(RefMode::ByRefFake)
                .try_build()
                .into_string(),
        })
    }
}

fn find_out_parameters(
    env: &Env,
    function: &Function,
    configured_functions: &[&config::functions::Function],
) -> Vec<String> {
    let index_to_ignore = find_index_to_ignore(&function.parameters, Some(&function.ret));
    function
        .parameters
        .iter()
        .enumerate()
        .filter(|&(index, param)| {
            Some(index) != index_to_ignore
                && param.direction == ParameterDirection::Out
                && param.name != "error"
        })
        .map(|(_, param)| {
            // FIXME: This should work completely based on the analysis of the finish() function
            // but that a) happens afterwards and b) is not accessible from here either.
            let nullable = configured_functions
                .iter()
                .find_map(|f| {
                    f.parameters
                        .iter()
                        .filter(|p| p.ident.is_match(&param.name))
                        .find_map(|p| p.nullable)
                })
                .unwrap_or(param.nullable);

            RustType::builder(env, param.typ)
                .direction(param.direction)
                .nullable(nullable)
                .try_build()
                .into_string()
        })
        .collect()
}

fn format_out_parameters(parameters: &[String]) -> String {
    if parameters.len() == 1 {
        parameters[0].to_string()
    } else {
        format!("({})", parameters.join(", "))
    }
}

fn find_error_type(env: &Env, function: &Function) -> String {
    let error_param = function
        .parameters
        .iter()
        .find(|param| param.direction == ParameterDirection::Out && param.name == "error")
        .expect("error type");
    if let Type::Record(_) = env.type_(error_param.typ) {
        return RustType::builder(env, error_param.typ)
            .direction(error_param.direction)
            .try_build()
            .into_string();
    }
    panic!("cannot find error type")
}

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

    #[test]
    fn get_new_all() {
        let mut bounds: Bounds = Default::default();
        let typ = BoundType::IsA(None);
        bounds.add_parameter("a", "", typ.clone(), false);
        assert_eq!(bounds.iter().len(), 1);
        // Don't add second time
        bounds.add_parameter("a", "", typ.clone(), false);
        assert_eq!(bounds.iter().len(), 1);
        bounds.add_parameter("b", "", typ.clone(), false);
        bounds.add_parameter("c", "", typ.clone(), false);
        bounds.add_parameter("d", "", typ.clone(), false);
        bounds.add_parameter("e", "", typ.clone(), false);
        bounds.add_parameter("f", "", typ.clone(), false);
        bounds.add_parameter("g", "", typ.clone(), false);
        bounds.add_parameter("h", "", typ.clone(), false);
        assert_eq!(bounds.iter().len(), 8);
        bounds.add_parameter("h", "", typ.clone(), false);
        assert_eq!(bounds.iter().len(), 8);
        bounds.add_parameter("i", "", typ.clone(), false);
        bounds.add_parameter("j", "", typ.clone(), false);
        bounds.add_parameter("k", "", typ, false);
    }

    #[test]
    #[should_panic(expected = "No free type aliases!")]
    fn exhaust_type_parameters() {
        let mut bounds: Bounds = Default::default();
        let typ = BoundType::NoWrapper;
        for c in 'a'..='l' {
            // Should panic on `l` because all type parameters are exhausted
            bounds.add_parameter(c.to_string().as_str(), "", typ.clone(), false);
        }
    }

    #[test]
    fn get_parameter_bound() {
        let mut bounds: Bounds = Default::default();
        let typ = BoundType::NoWrapper;
        bounds.add_parameter("a", "", typ.clone(), false);
        bounds.add_parameter("b", "", typ.clone(), false);
        let bound = bounds.get_parameter_bound("a").unwrap();
        // `NoWrapper `bounds are expected to have an alias:
        assert_eq!(bound.alias, Some('P'));
        assert_eq!(bound.bound_type, typ);
        let bound = bounds.get_parameter_bound("b").unwrap();
        assert_eq!(bound.alias, Some('Q'));
        assert_eq!(bound.bound_type, typ);
        assert_eq!(bounds.get_parameter_bound("c"), None);
    }

    #[test]
    fn impl_bound() {
        let mut bounds: Bounds = Default::default();
        let typ = BoundType::IsA(None);
        bounds.add_parameter("a", "", typ.clone(), false);
        bounds.add_parameter("b", "", typ.clone(), false);
        let bound = bounds.get_parameter_bound("a").unwrap();
        // `IsA` is simplified to an inline `foo: impl IsA<Bar>` and
        // lacks an alias/type-parameter:
        assert_eq!(bound.alias, None);
        assert_eq!(bound.bound_type, typ);

        let typ = BoundType::AsRef(None);
        bounds.add_parameter("c", "", typ.clone(), false);
        let bound = bounds.get_parameter_bound("c").unwrap();
        // Same `impl AsRef<Foo>` simplification as `IsA`:
        assert_eq!(bound.alias, None);
        assert_eq!(bound.bound_type, typ);
    }
}
<|endoftext|>


<commit_before>#![allow(deprecated)]

use ffi::bindings::*;

bitflags! {
    /// Flags used to specify why a GPU is throttling.
    // Checked against local
    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
    pub struct ThrottleReasons: u64 {
        /// Nothing is running on the GPU.
        ///
        /// This limiter may be removed in a future release.
        const GPU_IDLE                    = nvmlClocksThrottleReasonGpuIdle as u64;
        /// GPU clocks are limited by the current applications clocks setting.
        const APPLICATIONS_CLOCKS_SETTING = nvmlClocksThrottleReasonApplicationsClocksSetting as u64;
        #[deprecated(note = "Renamed to `APPLICATIONS_CLOCKS_SETTING`.")]
        const USER_DEFINED_CLOCKS         = nvmlClocksThrottleReasonUserDefinedClocks as u64;
        /// Software power scaling algorithm is reducing clocks.
        const SW_POWER_CAP                = nvmlClocksThrottleReasonSwPowerCap as u64;
        /**
        Hardware slowdown (reducing the core clocks by a factor of 2 or more)
        is engaged.

        This is an indicator of:

        * Temperature being too high
        * External Power Brake Asseration being triggered (e.g. by the system power supply)
        * Power draw being too high and Fast Trigger protection reducing the clocks

        This may also be reported during powerstate or clock change, behavior that may be
        removed in a later release.
        */
        const HW_SLOWDOWN                 = nvmlClocksThrottleReasonHwSlowdown as u64;
        /**
        This GPU is being throttled by another GPU in its sync boost group.

        Sync boost groups can be used to maximize performance per watt. All GPUs
        in a sync boost group will boost to the minimum possible clocks across
        the entire group. Look at the throttle reasons for other GPUs in the
        system to find out why this GPU is being held at lower clocks.
        */
        const SYNC_BOOST                  = nvmlClocksThrottleReasonSyncBoost as u64;
        /**
        Software thermal slowdown.

        This is an indicator of one or more of the following:

        * The current GPU temperature is above the max GPU operating temperature
        * The current memory temperature is above the max memory operating temperature
        */
        const SW_THERMAL_SLOWDOWN         = nvmlClocksThrottleReasonSwThermalSlowdown as u64;
        /**
        Hardware thermal slowdown is engaged, reducing core clocks by 2x or more.

        This indicates that the temperature of the GPU is too high.
        */
        const HW_THERMAL_SLOWDOWN         = nvmlClocksThrottleReasonHwThermalSlowdown as u64;
        /**
        Hardware power brake slowdown is engaged, reducing core clocks by 2x or more.

        This indicates that an external power brake assertion is being triggered,
        such as by the system power supply.
        */
        const HW_POWER_BRAKE_SLOWDOWN     = nvmlClocksThrottleReasonHwPowerBrakeSlowdown as u64;
        /// Clocks are as high as possible and are not being throttled.
        const NONE                        = nvmlClocksThrottleReasonNone as u64;
    }
}
<commit_msg>Add new throttle reason from NVML 10<commit_after>#![allow(deprecated)]

use ffi::bindings::*;

bitflags! {
    /// Flags used to specify why a GPU is throttling.
    // Checked against local
    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
    pub struct ThrottleReasons: u64 {
        /// Nothing is running on the GPU.
        ///
        /// This limiter may be removed in a future release.
        const GPU_IDLE                    = nvmlClocksThrottleReasonGpuIdle as u64;
        /// GPU clocks are limited by the current applications clocks setting.
        const APPLICATIONS_CLOCKS_SETTING = nvmlClocksThrottleReasonApplicationsClocksSetting as u64;
        #[deprecated(note = "Renamed to `APPLICATIONS_CLOCKS_SETTING`.")]
        const USER_DEFINED_CLOCKS         = nvmlClocksThrottleReasonUserDefinedClocks as u64;
        /// Software power scaling algorithm is reducing clocks.
        const SW_POWER_CAP                = nvmlClocksThrottleReasonSwPowerCap as u64;
        /**
        Hardware slowdown (reducing the core clocks by a factor of 2 or more)
        is engaged.

        This is an indicator of:

        * Temperature being too high
        * External Power Brake Asseration being triggered (e.g. by the system power supply)
        * Power draw being too high and Fast Trigger protection reducing the clocks

        This may also be reported during powerstate or clock change, behavior that may be
        removed in a later release.
        */
        const HW_SLOWDOWN                 = nvmlClocksThrottleReasonHwSlowdown as u64;
        /**
        This GPU is being throttled by another GPU in its sync boost group.

        Sync boost groups can be used to maximize performance per watt. All GPUs
        in a sync boost group will boost to the minimum possible clocks across
        the entire group. Look at the throttle reasons for other GPUs in the
        system to find out why this GPU is being held at lower clocks.
        */
        const SYNC_BOOST                  = nvmlClocksThrottleReasonSyncBoost as u64;
        /**
        Software thermal slowdown.

        This is an indicator of one or more of the following:

        * The current GPU temperature is above the max GPU operating temperature
        * The current memory temperature is above the max memory operating temperature
        */
        const SW_THERMAL_SLOWDOWN         = nvmlClocksThrottleReasonSwThermalSlowdown as u64;
        /**
        Hardware thermal slowdown is engaged, reducing core clocks by 2x or more.

        This indicates that the temperature of the GPU is too high.
        */
        const HW_THERMAL_SLOWDOWN         = nvmlClocksThrottleReasonHwThermalSlowdown as u64;
        /**
        Hardware power brake slowdown is engaged, reducing core clocks by 2x or more.

        This indicates that an external power brake assertion is being triggered,
        such as by the system power supply.
        */
        const HW_POWER_BRAKE_SLOWDOWN     = nvmlClocksThrottleReasonHwPowerBrakeSlowdown as u64;
        /// GPU clocks are limited by the current setting of display clocks.
        const DISPLAY_CLOCK_SETTING       = nvmlClocksThrottleReasonDisplayClockSetting as u64;
        /// Clocks are as high as possible and are not being throttled.
        const NONE                        = nvmlClocksThrottleReasonNone as u64;
    }
}
<|endoftext|>


<commit_before>//! Implementation of `make clean` in rustbuild.
//!
//! Responsible for cleaning out a build directory of all old and stale
//! artifacts to prepare for a fresh build. Currently doesn't remove the
//! `build/cache` directory (download cache) or the `build/$target/llvm`
//! directory unless the `--all` flag is present.

use std::fs;
use std::io::{self, ErrorKind};
use std::path::Path;

use build_helper::t;

use crate::Build;

pub fn clean(build: &Build, all: bool) {
    rm_rf("tmp".as_ref());

    if all {
        rm_rf(&build.out);
    } else {
        rm_rf(&build.out.join("tmp"));
        rm_rf(&build.out.join("dist"));
        rm_rf(&build.out.join("bootstrap"));

        for host in &build.hosts {
            let entries = match build.out.join(host.triple).read_dir() {
                Ok(iter) => iter,
                Err(_) => continue,
            };

            for entry in entries {
                let entry = t!(entry);
                if entry.file_name().to_str() == Some("llvm") {
                    continue;
                }
                let path = t!(entry.path().canonicalize());
                rm_rf(&path);
            }
        }
    }
}

fn rm_rf(path: &Path) {
    match path.symlink_metadata() {
        Err(e) => {
            if e.kind() == ErrorKind::NotFound {
                return;
            }
            panic!("failed to get metadata for file {}: {}", path.display(), e);
        }
        Ok(metadata) => {
            if metadata.file_type().is_file() || metadata.file_type().is_symlink() {
                do_op(path, "remove file", |p| {
                    fs::remove_file(p).or_else(|e| {
                        // Work around the fact that we cannot
                        // delete an executable while it runs on Windows.
                        #[cfg(windows)]
                        if e.kind() == std::io::ErrorKind::PermissionDenied
                            && p.file_name().and_then(std::ffi::OsStr::to_str)
                                == Some("bootstrap.exe")
                        {
                            eprintln!("warning: failed to delete '{}'.", p.display());
                            return Ok(());
                        }
                        Err(e)
                    })
                });
                return;
            }

            for file in t!(fs::read_dir(path)) {
                rm_rf(&t!(file).path());
            }
            do_op(path, "remove dir", |p| {
                fs::remove_dir(p).or_else(|e| {
                    // Check for dir not empty on Windows
                    #[cfg(windows)]
                    if matches!(e.kind(), std::io::ErrorKind::Other)
                        && e.raw_os_error() == Some(145)
                    {
                        return Ok(());
                    }

                    Err(e)
                })
            });
        }
    };
}

fn do_op<F>(path: &Path, desc: &str, mut f: F)
where
    F: FnMut(&Path) -> io::Result<()>,
{
    match f(path) {
        Ok(()) => {}
        // On windows we can't remove a readonly file, and git will often clone files as readonly.
        // As a result, we have some special logic to remove readonly files on windows.
        // This is also the reason that we can't use things like fs::remove_dir_all().
        Err(ref e) if cfg!(windows) && e.kind() == ErrorKind::PermissionDenied => {
            let mut p = t!(path.symlink_metadata()).permissions();
            p.set_readonly(false);
            t!(fs::set_permissions(path, p));
            f(path).unwrap_or_else(|e| {
                // Deleting symlinked directories on Windows is non-trivial.
                // Skip doing so for now.
                #[cfg(windows)]
                if e.kind() == ErrorKind::PermissionDenied && path.is_dir() {
                    eprintln!("warning: failed to delete '{}'.", path.display());
                    return;
                }
                panic!("failed to {} {}: {}", desc, path.display(), e);
            });
        }
        Err(e) => {
            panic!("failed to {} {}: {}", desc, path.display(), e);
        }
    }
}
<commit_msg>Delete symlinked directories<commit_after>//! Implementation of `make clean` in rustbuild.
//!
//! Responsible for cleaning out a build directory of all old and stale
//! artifacts to prepare for a fresh build. Currently doesn't remove the
//! `build/cache` directory (download cache) or the `build/$target/llvm`
//! directory unless the `--all` flag is present.

use std::fs;
use std::io::{self, ErrorKind};
use std::path::Path;

use build_helper::t;

use crate::Build;

pub fn clean(build: &Build, all: bool) {
    rm_rf("tmp".as_ref());

    if all {
        rm_rf(&build.out);
    } else {
        rm_rf(&build.out.join("tmp"));
        rm_rf(&build.out.join("dist"));
        rm_rf(&build.out.join("bootstrap"));

        for host in &build.hosts {
            let entries = match build.out.join(host.triple).read_dir() {
                Ok(iter) => iter,
                Err(_) => continue,
            };

            for entry in entries {
                let entry = t!(entry);
                if entry.file_name().to_str() == Some("llvm") {
                    continue;
                }
                let path = t!(entry.path().canonicalize());
                rm_rf(&path);
            }
        }
    }
}

fn rm_rf(path: &Path) {
    match path.symlink_metadata() {
        Err(e) => {
            if e.kind() == ErrorKind::NotFound {
                return;
            }
            panic!("failed to get metadata for file {}: {}", path.display(), e);
        }
        Ok(metadata) => {
            if metadata.file_type().is_file() || metadata.file_type().is_symlink() {
                do_op(path, "remove file", |p| {
                    fs::remove_file(p).or_else(|e| {
                        // Work around the fact that we cannot
                        // delete an executable while it runs on Windows.
                        #[cfg(windows)]
                        if e.kind() == std::io::ErrorKind::PermissionDenied
                            && p.file_name().and_then(std::ffi::OsStr::to_str)
                                == Some("bootstrap.exe")
                        {
                            eprintln!("warning: failed to delete '{}'.", p.display());
                            return Ok(());
                        }
                        Err(e)
                    })
                });
                return;
            }

            for file in t!(fs::read_dir(path)) {
                rm_rf(&t!(file).path());
            }
            do_op(path, "remove dir", |p| {
                fs::remove_dir(p).or_else(|e| {
                    // Check for dir not empty on Windows
                    #[cfg(windows)]
                    if matches!(e.kind(), std::io::ErrorKind::Other)
                        && e.raw_os_error() == Some(145)
                    {
                        return Ok(());
                    }

                    Err(e)
                })
            });
        }
    };
}

fn do_op<F>(path: &Path, desc: &str, mut f: F)
where
    F: FnMut(&Path) -> io::Result<()>,
{
    match f(path) {
        Ok(()) => {}
        // On windows we can't remove a readonly file, and git will often clone files as readonly.
        // As a result, we have some special logic to remove readonly files on windows.
        // This is also the reason that we can't use things like fs::remove_dir_all().
        Err(ref e) if cfg!(windows) && e.kind() == ErrorKind::PermissionDenied => {
            let m = t!(path.symlink_metadata());
            let mut p = m.permissions();
            p.set_readonly(false);
            t!(fs::set_permissions(path, p));
            f(path).unwrap_or_else(|e| {
                // Delete symlinked directories on Windows
                #[cfg(windows)]
                if m.file_type().is_symlink() && path.is_dir() && fs::remove_dir(path).is_ok() {
                    return;
                }
                panic!("failed to {} {}: {}", desc, path.display(), e);
            });
        }
        Err(e) => {
            panic!("failed to {} {}: {}", desc, path.display(), e);
        }
    }
}
<|endoftext|>