<commit_before>use std::{
    ffi::{CStr, CString},
    fs::File,
    io::{Seek, Write},
    os::unix::prelude::{AsRawFd, FromRawFd, RawFd},
};

use nix::{
    fcntl::{FcntlArg, SealFlag},
    sys::memfd::MemFdCreateFlag,
};
use slog::error;

#[derive(Debug)]
#[allow(dead_code)]
pub(crate) struct KeymapFile {
    sealed: Option<SealedFile>,
    keymap: CString,
}

impl KeymapFile {
    pub fn new(keymap: CString, log: slog::Logger) -> Self {
        let sealed = SealedFile::new(&keymap);

        if let Err(err) = sealed.as_ref() {
            error!(log, "Error when creating sealed keymap file: {}", err);
        }

        Self {
            sealed: sealed.ok(),
            keymap,
        }
    }

    pub(crate) fn change_keymap(&mut self, keymap: CString, log: slog::Logger) {
        let sealed = SealedFile::new(&keymap);

        if let Err(err) = sealed.as_ref() {
            error!(log, "Error when creating sealed keymap file: {}", err);
        }

        self.sealed = sealed.ok();
        self.keymap = keymap;
    }

    #[cfg(feature = "wayland_frontend")]
    pub fn with_fd<F>(&self, supports_sealed: bool, cb: F) -> Result<(), std::io::Error>
    where
        F: FnOnce(RawFd, usize),
    {
        use std::path::PathBuf;

        if let Some(file) = supports_sealed.then(|| self.sealed.as_ref()).flatten() {
            cb(file.as_raw_fd(), file.size);
            Ok(())
        } else {
            let dir = std::env::var_os("XDG_RUNTIME_DIR")
                .map(PathBuf::from)
                .unwrap_or_else(std::env::temp_dir);

            let keymap = self.keymap.as_bytes_with_nul();
            let mut file = tempfile::tempfile_in(dir)?;
            file.write_all(keymap)?;
            file.flush()?;

            cb(file.as_raw_fd(), keymap.len());
            Ok(())
        }
    }
}

#[derive(Debug)]
struct SealedFile {
    file: File,
    #[allow(dead_code)]
    size: usize,
}

impl SealedFile {
    fn new(keymap: &CStr) -> Result<Self, std::io::Error> {
        let name = CString::new("smithay-keymap").expect("File name should not contain interior nul byte");
        let keymap = keymap.to_bytes_with_nul();

        let fd = nix::sys::memfd::memfd_create(
            &name,
            MemFdCreateFlag::MFD_CLOEXEC | MemFdCreateFlag::MFD_ALLOW_SEALING,
        )?;

        let mut file = unsafe { File::from_raw_fd(fd) };
        file.write_all(keymap)?;
        file.flush()?;

        file.seek(std::io::SeekFrom::Start(0))?;

        nix::fcntl::fcntl(
            file.as_raw_fd(),
            FcntlArg::F_ADD_SEALS(
                SealFlag::F_SEAL_SEAL
                    | SealFlag::F_SEAL_SHRINK
                    | SealFlag::F_SEAL_GROW
                    | SealFlag::F_SEAL_WRITE,
            ),
        )?;

        Ok(Self {
            file,
            size: keymap.len(),
        })
    }
}

impl AsRawFd for SealedFile {
    fn as_raw_fd(&self) -> RawFd {
        self.file.as_raw_fd()
    }
}
<commit_msg>All keymap change functions movced to frontend<commit_after>use std::{
    ffi::{CStr, CString},
    fs::File,
    io::{Seek, Write},
    os::unix::prelude::{AsRawFd, FromRawFd, RawFd},
};

use nix::{
    fcntl::{FcntlArg, SealFlag},
    sys::memfd::MemFdCreateFlag,
};
use slog::error;

#[derive(Debug)]
#[allow(dead_code)]
pub(crate) struct KeymapFile {
    sealed: Option<SealedFile>,
    keymap: CString,
}

impl KeymapFile {
    pub fn new(keymap: CString, log: slog::Logger) -> Self {
        let sealed = SealedFile::new(&keymap);

        if let Err(err) = sealed.as_ref() {
            error!(log, "Error when creating sealed keymap file: {}", err);
        }

        Self {
            sealed: sealed.ok(),
            keymap,
        }
    }

    #[cfg(feature = "wayland_frontend")]
    pub(crate) fn change_keymap(&mut self, keymap: CString, log: slog::Logger) {
        let sealed = SealedFile::new(&keymap);

        if let Err(err) = sealed.as_ref() {
            error!(log, "Error when creating sealed keymap file: {}", err);
        }

        self.sealed = sealed.ok();
        self.keymap = keymap;
    }

    #[cfg(feature = "wayland_frontend")]
    pub fn with_fd<F>(&self, supports_sealed: bool, cb: F) -> Result<(), std::io::Error>
    where
        F: FnOnce(RawFd, usize),
    {
        use std::path::PathBuf;

        if let Some(file) = supports_sealed.then(|| self.sealed.as_ref()).flatten() {
            cb(file.as_raw_fd(), file.size);
            Ok(())
        } else {
            let dir = std::env::var_os("XDG_RUNTIME_DIR")
                .map(PathBuf::from)
                .unwrap_or_else(std::env::temp_dir);

            let keymap = self.keymap.as_bytes_with_nul();
            let mut file = tempfile::tempfile_in(dir)?;
            file.write_all(keymap)?;
            file.flush()?;

            cb(file.as_raw_fd(), keymap.len());
            Ok(())
        }
    }
}

#[derive(Debug)]
struct SealedFile {
    file: File,
    #[allow(dead_code)]
    size: usize,
}

impl SealedFile {
    fn new(keymap: &CStr) -> Result<Self, std::io::Error> {
        let name = CString::new("smithay-keymap").expect("File name should not contain interior nul byte");
        let keymap = keymap.to_bytes_with_nul();

        let fd = nix::sys::memfd::memfd_create(
            &name,
            MemFdCreateFlag::MFD_CLOEXEC | MemFdCreateFlag::MFD_ALLOW_SEALING,
        )?;

        let mut file = unsafe { File::from_raw_fd(fd) };
        file.write_all(keymap)?;
        file.flush()?;

        file.seek(std::io::SeekFrom::Start(0))?;

        nix::fcntl::fcntl(
            file.as_raw_fd(),
            FcntlArg::F_ADD_SEALS(
                SealFlag::F_SEAL_SEAL
                    | SealFlag::F_SEAL_SHRINK
                    | SealFlag::F_SEAL_GROW
                    | SealFlag::F_SEAL_WRITE,
            ),
        )?;

        Ok(Self {
            file,
            size: keymap.len(),
        })
    }
}

impl AsRawFd for SealedFile {
    fn as_raw_fd(&self) -> RawFd {
        self.file.as_raw_fd()
    }
}
<|endoftext|>


<commit_before>//
// gash.rs
//
// Starting code for PS2
// Running on Rust 0.9
//
// University of Virginia - cs4414 Spring 2014
// Weilin Xu, David Evans
// Version 0.4
//

extern mod extra;

use std::{run, os};
use std::io::buffered::BufferedReader;
use std::path::posix::Path;
use std::option::Option;
use std::io::{stdin, stdio, IoError, io_error, File, Truncate, Write};
use std::run::Process;
use std::io::process::ProcessExit;
use std::run::ProcessOptions;
use std::comm::Port;
use extra::getopts;
use std::io::signal::{Listener, Interrupt, Signum};

struct Cmd {
    program : ~str,
    argv : ~[~str],
}

impl Cmd {
    fn new(cmd_name: &str) -> Option<Cmd> {
        let mut argv: ~[~str] = split_words(cmd_name);
        if argv.len() > 0 {
            let program: ~str = argv.remove(0);
            let argv : ~[~str] = argv;
            Some(Cmd {
                program : program,
                argv : argv,
            })
        }
        else {
            None
        }
    }
}

trait Command {
    fn run(&mut self) -> Option<Process>;
}

struct CmdProcess {
    command     : ~str,
    args        : ~[~str],
    stdin       : Option<i32>,
    stdout      : Option<i32>,
}
impl CmdProcess {
    fn new(cmd : Cmd,
           stdin : Option<i32>,
           stdout : Option<i32>)
        -> Option<CmdProcess>
    {
        if (cmd_exists(&cmd)) {
            Some(CmdProcess {
                command     : cmd.program.to_owned(),
                args        : cmd.argv.to_owned(),
                stdin       : stdin,
                stdout      : stdout,
            })
        }
        else { None }
    }

    /*
    fn set_stdin(&mut self, stdin : Option<i32>) {
        self.stdin = stdin;
    }

    fn set_stdout(&mut self, stdout : Option<i32>) {
        self.stdout = stdout;
    }
    */
}
impl Command for CmdProcess {
    fn run(&mut self) -> Option<Process> {
        let command = self.command.to_owned();
        let args = self.args.to_owned();
        let options = ProcessOptions {
            env    : None,
            dir    : None,
            in_fd  : self.stdin,
            out_fd : self.stdout,
            err_fd : None,
        };
        Process::new(command, args, options)
    }
}

struct BackgroundProcess {
    command      : ~str,
    args         : ~[~str],
    exit_port    : Option<Port<ProcessExit>>,
    kill_chan    : Option<Chan<int>>,
}
impl BackgroundProcess {
    fn new(cmd : Cmd) -> Option<BackgroundProcess> {
        if (cmd_exists(&cmd)) {
            Some(BackgroundProcess {
                command: cmd.program.to_owned(),
                args: cmd.argv.to_owned(),
                exit_port: None,
                kill_chan: None,
            })
        }
        else { None }
    }
    fn run(&mut self) {
        let (port, chan) : (Port<ProcessExit>, Chan<ProcessExit>)
                = Chan::new();
        let (killport, killchan) : (Port<int>, Chan<int>) = Chan::new();
        let command = self.command.to_owned();
        let args = self.args.to_owned();
        spawn(proc() {
            let options = ProcessOptions {
                env    : None,
                dir    : None,
                in_fd  : None,
                out_fd : None,
                err_fd : None,
            };
            let maybe_process = Process::new(command, args, options);
            match maybe_process {
            Some(mut process) => {
                let signal = killport.recv();
                let mut error = None;
                println("Background signal handler started");
                match signal {
                9 => {
                    io_error::cond.trap(|e: IoError| {
                        error = Some(e);
                        }).inside(|| {
                            process.force_destroy()
                        })
                }
                15 => {
                    io_error::cond.trap(|e: IoError| {
                        error = Some(e);
                        }).inside(|| {
                            process.force_destroy()
                        })
                }
                _ => {
                }}
                chan.try_send_deferred(process.finish());
            }
            None => {
            }}
        });
        self.exit_port = Some(port);
        self.kill_chan = Some(killchan);
    }
}

struct Shell {
    cmd_prompt : ~str,
    history    : ~[~str],
    processes  : ~[~BackgroundProcess],
    broken : bool,
}

impl Shell {
    fn new(prompt_str: &str) -> Shell {
        Shell {
            cmd_prompt: prompt_str.to_owned(),
            history: ~[],
            processes: ~[],
            broken: false,
        }
    }

    fn run(&mut self) {
        let mut stdin = BufferedReader::new(stdin());
        loop {
            print(self.cmd_prompt);
            stdio::flush();

            let line = stdin.read_line().unwrap();
            let cmd_line = line.trim().to_owned();
            let program = cmd_line.splitn(' ', 1).nth(0).expect("no program");
            self.kill_dead();
            match program {
            "" => {}
            _  => {
                self.push_hist(cmd_line)
            }}

            match program {
            "" =>  {
                continue;
            }
            "exit" =>  {
                self.kill_all();
                return;
            }
            "history" => {
                self.show_hist();
            }
            "cd" =>  {
                self.chdir(cmd_line);
            }
            _ => {
                self.run_cmdline(cmd_line);
            }}
        }
    }

    fn kill_dead(&mut self) {
        let mut dead : ~[uint];
        dead = ~[];
        let mut i = 0;
        for cmd in self.processes.iter() {
            if is_dead(&cmd.exit_port) {
                dead.push(i);
            }
            i += 1;
        }
        for &p in dead.iter() {
            self.processes.remove(p);
        }
    }

    fn kill_all(&mut self) {
        for p in self.processes.iter() {
            match p.kill_chan {
            Some(ref kill_channel) => {
                kill_channel.try_send_deferred(15);
            }
            None => {
            }}
        }
    }

    fn push_hist(&mut self, cmd_line: &str) {
        &self.history.push(cmd_line.to_owned());
    }

    fn show_hist(&mut self) {
        println!("{:s}", self.get_hist());
    }

    fn get_hist(&mut self) -> ~str {
        let mut hist = ~"";
        for i in self.history.iter() {
            let s = i.to_owned();
            match hist {
                ~"" => { hist = s; }
                _   => { hist = hist + "\n" + s; }
            }
        }
        return hist
    }

    fn chdir(&mut self, cmd_line: &str) {
        let mut argv: ~[~str] = split_words(cmd_line);
        if argv.len() > 0 {
            argv.remove(0);
            let path = Path::new(argv[0]);
            os::change_dir(&path);
        }
    }

    fn run_cmdline(&mut self, cmd_line: &str) {
        if cmd_line.contains_char('>') {
            parse_r_redirect(cmd_line);
        }
        else if cmd_line.contains_char('<') {
            parse_l_redirect(cmd_line);
        }
        else if cmd_line.contains_char('|') {
            self.parse_pipeline(cmd_line);
        }
        else {
            self.parse_process(cmd_line);
        }
    }

    fn parse_process(&mut self, cmd_line : &str) -> Option<Process>{
        match Cmd::new(cmd_line) {
        Some(cmd) => {
            if (cmd.argv.len() > 0 && cmd.argv.last() == &~"&") {
                let mut argv = cmd.argv.to_owned();
                argv.pop();
                self.make_bg_process(Cmd{
                    program:cmd.program,
                    argv:argv});
                None
            }
            else {
                make_process(cmd, Some(0), Some(1))
            }
        }
        None => {
            None
        }}
    }

    fn parse_pipeline(&mut self, cmd_line : &str) -> Option<~run::Process> {
        let pair : ~[&str] = cmd_line.rsplitn('|', 1).collect();
        match Cmd::new(pair[0].trim()) {
        Some(cmd) => {
            if pair.len() > 1 {
                let two = self.parse_pipeline(pair[1].trim());
                self.pipe_input(cmd, two)
            }
            else {
                match make_process(cmd, None, None) {
                Some(process) => Some(~process),
                None => None
                }
            }
        }
        None => {
            None
        }}
    }

    fn pipe_input(&mut self,
                  cmd : Cmd,
                  two : Option<~run::Process>) -> Option<~run::Process> {
        match make_process(cmd, None, Some(1)) {
        Some(mut process) => {
            match two {
            Some(mut p2) => {
                let output = p2.finish_with_output();
                if output.status.success() {
                    process.input().write(output.output);
                    process.close_input();
                    process.finish();
                }
                Some(~process)
            }
            None => {
                println("No two remain.");
                Some(~process)
            }}
        }
        None => {
            None
        }}
    }

    fn make_bg_process(&mut self, cmd : Cmd) {
        match BackgroundProcess::new(cmd) {
        Some(mut process) => {
            process.run();
            self.processes.push(~process);
        }
        None          => {
        }}
    }
}

fn get_cmdline_from_args() -> Option<~str> {
    /* Begin processing program arguments and initiate the parameters. */
    let args = os::args();

    let opts = ~[
        getopts::optopt("c")
    ];

    let matches = match getopts::getopts(args.tail(), opts) {
        Ok(m) => { m }
        Err(f) => { fail!(f.to_err_msg()) }
    };

    if matches.opt_present("c") {
        let cmd_str = match matches.opt_str("c") {
                      Some(cmd_str) => {cmd_str.to_owned()},
                      None          => {~""}
        };
        return Some(cmd_str);
    } else {
        return None;
    }
}

fn cmd_exists(command : &Cmd) -> bool {
    let ret = run::process_output("which", [command.program.to_owned()]);
    return ret.expect("exit code error.").status.success();
}

fn is_dead(exit_port : &Option<Port<ProcessExit>>) -> bool {
    let mut dead = false;
    match *exit_port {
    Some(ref p) => {
        println("DEBUG: doing shutdown");
        match p.try_recv() {
        Some (exitstatus) => {
            dead = exitstatus.success();
        },
        None => {
        }}
    }
    _ => {
    }}
    dead
}

fn split_words(word : &str) -> ~[~str] {
    word.split(' ').filter_map(
        |x| if x != "" { Some(x.to_owned()) } else { None }
        ).to_owned_vec()
}

fn make_process(cmd : Cmd,
                stdin: Option<i32>,
                stdout: Option<i32>) -> Option<run::Process> {
    match CmdProcess::new(cmd, stdin, stdout) {
    Some(mut cmdprocess) => {
        cmdprocess.run()
    }
    None => {
        None
    }}
}

fn parse_l_redirect(cmd_line : &str) {
    let pair : ~[&str] = cmd_line.rsplit('<').collect();
    let filename = pair[0].trim();
    match Cmd::new(pair[1].trim()) {
    Some(cmd) => {
        match make_process(cmd, None, Some(1)) {
        Some(mut process) => {
            match File::open_mode(&Path::new(filename),
                                    std::io::Append,
                                    std::io::ReadWrite) {
            Some(file) => {
                let proc_input = process.input();
                let mut file_buffer = BufferedReader::new(file);
                proc_input.write(file_buffer.read_to_end());
            }
            None => {
            }}
        }
        None => {
        }}
    }
    None => {
    }}
}

fn write_output_to_file(output : std::run::ProcessOutput,
                        filename : &str) {
    if output.status.success() {
        match File::open_mode(&Path::new(filename),
                                std::io::Truncate,
                                std::io::Write)
        {
            Some(mut file) => {
                file.write(output.output);
            }
            None =>{
                println!("Opening {:s} failed!", filename);
            }
        }
    }
    else {
        println!("{:?}", output.error);
    }
}

fn parse_r_redirect(cmd_line : &str) {
    let pair : ~[&str] = cmd_line.rsplit('>').collect();
    let file = pair[0].trim();
    //let command = pair[1].trim();
    match Cmd::new(pair[1].trim()) {
    Some(cmd) => {
        match make_process(cmd, None, None) {
        Some(mut process) => {
            write_output_to_file(
                process.finish_with_output(),
                file);
        }
        None => {
        }}
    }
    None => {
    }}
}

fn rupt(port: Port<Signum>) {
    do spawn {
        loop{
            match port.recv_opt() {
            Some(recv) => {
                match recv {
                Interrupt => {
                    println("Got Interrupt'ed");
                }
                _ => {
                    println("nothing");
                }}
            }
            None => {
            }}
        }
    }
    Shell::new("gash > ").run();
}

fn main() {
    let opt_cmd_line = get_cmdline_from_args();

    match opt_cmd_line {
    Some(cmd_line) => {
        let mut shell = Shell::new("");
        shell.run_cmdline(cmd_line);
    }
    None => {
        let mut listener = Listener::new();
        listener.register(Interrupt);
        rupt(listener.port);
    }}
}
<commit_msg>remove debug printlns<commit_after>//
// gash.rs
//
// Starting code for PS2
// Running on Rust 0.9
//
// University of Virginia - cs4414 Spring 2014
// Weilin Xu, David Evans
// Version 0.4
//

extern mod extra;

use std::{run, os};
use std::io::buffered::BufferedReader;
use std::path::posix::Path;
use std::option::Option;
use std::io::{stdin, stdio, IoError, io_error, File, Truncate, Write};
use std::run::Process;
use std::io::process::ProcessExit;
use std::run::ProcessOptions;
use std::comm::Port;
use extra::getopts;
use std::io::signal::{Listener, Interrupt, Signum};

struct Cmd {
    program : ~str,
    argv : ~[~str],
}

impl Cmd {
    fn new(cmd_name: &str) -> Option<Cmd> {
        let mut argv: ~[~str] = split_words(cmd_name);
        if argv.len() > 0 {
            let program: ~str = argv.remove(0);
            let argv : ~[~str] = argv;
            Some(Cmd {
                program : program,
                argv : argv,
            })
        }
        else {
            None
        }
    }
}

trait Command {
    fn run(&mut self) -> Option<Process>;
}

struct CmdProcess {
    command     : ~str,
    args        : ~[~str],
    stdin       : Option<i32>,
    stdout      : Option<i32>,
}
impl CmdProcess {
    fn new(cmd : Cmd,
           stdin : Option<i32>,
           stdout : Option<i32>)
        -> Option<CmdProcess>
    {
        if (cmd_exists(&cmd)) {
            Some(CmdProcess {
                command     : cmd.program.to_owned(),
                args        : cmd.argv.to_owned(),
                stdin       : stdin,
                stdout      : stdout,
            })
        }
        else { None }
    }

    /*
    fn set_stdin(&mut self, stdin : Option<i32>) {
        self.stdin = stdin;
    }

    fn set_stdout(&mut self, stdout : Option<i32>) {
        self.stdout = stdout;
    }
    */
}
impl Command for CmdProcess {
    fn run(&mut self) -> Option<Process> {
        let command = self.command.to_owned();
        let args = self.args.to_owned();
        let options = ProcessOptions {
            env    : None,
            dir    : None,
            in_fd  : self.stdin,
            out_fd : self.stdout,
            err_fd : None,
        };
        Process::new(command, args, options)
    }
}

struct BackgroundProcess {
    command      : ~str,
    args         : ~[~str],
    exit_port    : Option<Port<ProcessExit>>,
    kill_chan    : Option<Chan<int>>,
}
impl BackgroundProcess {
    fn new(cmd : Cmd) -> Option<BackgroundProcess> {
        if (cmd_exists(&cmd)) {
            Some(BackgroundProcess {
                command: cmd.program.to_owned(),
                args: cmd.argv.to_owned(),
                exit_port: None,
                kill_chan: None,
            })
        }
        else { None }
    }
    fn run(&mut self) {
        let (port, chan) : (Port<ProcessExit>, Chan<ProcessExit>)
                = Chan::new();
        let (killport, killchan) : (Port<int>, Chan<int>) = Chan::new();
        let command = self.command.to_owned();
        let args = self.args.to_owned();
        spawn(proc() {
            let options = ProcessOptions {
                env    : None,
                dir    : None,
                in_fd  : None,
                out_fd : None,
                err_fd : None,
            };
            let maybe_process = Process::new(command, args, options);
            match maybe_process {
            Some(mut process) => {
                let signal = killport.recv();
                let mut error = None;
                match signal {
                9 => {
                    io_error::cond.trap(|e: IoError| {
                        error = Some(e);
                        }).inside(|| {
                            process.force_destroy()
                        })
                }
                15 => {
                    io_error::cond.trap(|e: IoError| {
                        error = Some(e);
                        }).inside(|| {
                            process.force_destroy()
                        })
                }
                _ => {
                }}
                chan.try_send_deferred(process.finish());
            }
            None => {
            }}
        });
        self.exit_port = Some(port);
        self.kill_chan = Some(killchan);
    }
}

struct Shell {
    cmd_prompt : ~str,
    history    : ~[~str],
    processes  : ~[~BackgroundProcess],
    broken : bool,
}

impl Shell {
    fn new(prompt_str: &str) -> Shell {
        Shell {
            cmd_prompt: prompt_str.to_owned(),
            history: ~[],
            processes: ~[],
            broken: false,
        }
    }

    fn run(&mut self) {
        let mut stdin = BufferedReader::new(stdin());
        loop {
            print(self.cmd_prompt);
            stdio::flush();

            let line = stdin.read_line().unwrap();
            let cmd_line = line.trim().to_owned();
            let program = cmd_line.splitn(' ', 1).nth(0).expect("no program");
            self.kill_dead();
            match program {
            "" => {}
            _  => {
                self.push_hist(cmd_line)
            }}

            match program {
            "" =>  {
                continue;
            }
            "exit" =>  {
                self.kill_all();
                return;
            }
            "history" => {
                self.show_hist();
            }
            "cd" =>  {
                self.chdir(cmd_line);
            }
            _ => {
                self.run_cmdline(cmd_line);
            }}
        }
    }

    fn kill_dead(&mut self) {
        let mut dead : ~[uint];
        dead = ~[];
        let mut i = 0;
        for cmd in self.processes.iter() {
            if is_dead(&cmd.exit_port) {
                dead.push(i);
            }
            i += 1;
        }
        for &p in dead.iter() {
            self.processes.remove(p);
        }
    }

    fn kill_all(&mut self) {
        for p in self.processes.iter() {
            match p.kill_chan {
            Some(ref kill_channel) => {
                kill_channel.try_send_deferred(15);
            }
            None => {
            }}
        }
    }

    fn push_hist(&mut self, cmd_line: &str) {
        &self.history.push(cmd_line.to_owned());
    }

    fn show_hist(&mut self) {
        println!("{:s}", self.get_hist());
    }

    fn get_hist(&mut self) -> ~str {
        let mut hist = ~"";
        for i in self.history.iter() {
            let s = i.to_owned();
            match hist {
                ~"" => { hist = s; }
                _   => { hist = hist + "\n" + s; }
            }
        }
        return hist
    }

    fn chdir(&mut self, cmd_line: &str) {
        let mut argv: ~[~str] = split_words(cmd_line);
        if argv.len() > 0 {
            argv.remove(0);
            let path = Path::new(argv[0]);
            os::change_dir(&path);
        }
    }

    fn run_cmdline(&mut self, cmd_line: &str) {
        if cmd_line.contains_char('>') {
            parse_r_redirect(cmd_line);
        }
        else if cmd_line.contains_char('<') {
            parse_l_redirect(cmd_line);
        }
        else if cmd_line.contains_char('|') {
            self.parse_pipeline(cmd_line);
        }
        else {
            self.parse_process(cmd_line);
        }
    }

    fn parse_process(&mut self, cmd_line : &str) -> Option<Process>{
        match Cmd::new(cmd_line) {
        Some(cmd) => {
            if (cmd.argv.len() > 0 && cmd.argv.last() == &~"&") {
                let mut argv = cmd.argv.to_owned();
                argv.pop();
                self.make_bg_process(Cmd{
                    program:cmd.program,
                    argv:argv});
                None
            }
            else {
                make_process(cmd, Some(0), Some(1))
            }
        }
        None => {
            None
        }}
    }

    fn parse_pipeline(&mut self, cmd_line : &str) -> Option<~run::Process> {
        let pair : ~[&str] = cmd_line.rsplitn('|', 1).collect();
        match Cmd::new(pair[0].trim()) {
        Some(cmd) => {
            if pair.len() > 1 {
                let two = self.parse_pipeline(pair[1].trim());
                self.pipe_input(cmd, two)
            }
            else {
                match make_process(cmd, None, None) {
                Some(process) => Some(~process),
                None => None
                }
            }
        }
        None => {
            None
        }}
    }

    fn pipe_input(&mut self,
                  cmd : Cmd,
                  two : Option<~run::Process>) -> Option<~run::Process> {
        match make_process(cmd, None, Some(1)) {
        Some(mut process) => {
            match two {
            Some(mut p2) => {
                let output = p2.finish_with_output();
                if output.status.success() {
                    process.input().write(output.output);
                    process.close_input();
                    process.finish();
                }
                Some(~process)
            }
            None => {
                println("No two remain.");
                Some(~process)
            }}
        }
        None => {
            None
        }}
    }

    fn make_bg_process(&mut self, cmd : Cmd) {
        match BackgroundProcess::new(cmd) {
        Some(mut process) => {
            process.run();
            self.processes.push(~process);
        }
        None          => {
        }}
    }
}

fn get_cmdline_from_args() -> Option<~str> {
    /* Begin processing program arguments and initiate the parameters. */
    let args = os::args();

    let opts = ~[
        getopts::optopt("c")
    ];

    let matches = match getopts::getopts(args.tail(), opts) {
        Ok(m) => { m }
        Err(f) => { fail!(f.to_err_msg()) }
    };

    if matches.opt_present("c") {
        let cmd_str = match matches.opt_str("c") {
                      Some(cmd_str) => {cmd_str.to_owned()},
                      None          => {~""}
        };
        return Some(cmd_str);
    } else {
        return None;
    }
}

fn cmd_exists(command : &Cmd) -> bool {
    let ret = run::process_output("which", [command.program.to_owned()]);
    return ret.expect("exit code error.").status.success();
}

fn is_dead(exit_port : &Option<Port<ProcessExit>>) -> bool {
    let mut dead = false;
    match *exit_port {
    Some(ref p) => {
        match p.try_recv() {
        Some (exitstatus) => {
            dead = exitstatus.success();
        },
        None => {
        }}
    }
    _ => {
    }}
    dead
}

fn split_words(word : &str) -> ~[~str] {
    word.split(' ').filter_map(
        |x| if x != "" { Some(x.to_owned()) } else { None }
        ).to_owned_vec()
}

fn make_process(cmd : Cmd,
                stdin: Option<i32>,
                stdout: Option<i32>) -> Option<run::Process> {
    match CmdProcess::new(cmd, stdin, stdout) {
    Some(mut cmdprocess) => {
        cmdprocess.run()
    }
    None => {
        None
    }}
}

fn parse_l_redirect(cmd_line : &str) {
    let pair : ~[&str] = cmd_line.rsplit('<').collect();
    let filename = pair[0].trim();
    match Cmd::new(pair[1].trim()) {
    Some(cmd) => {
        match make_process(cmd, None, Some(1)) {
        Some(mut process) => {
            match File::open_mode(&Path::new(filename),
                                    std::io::Append,
                                    std::io::ReadWrite) {
            Some(file) => {
                let proc_input = process.input();
                let mut file_buffer = BufferedReader::new(file);
                proc_input.write(file_buffer.read_to_end());
            }
            None => {
            }}
        }
        None => {
        }}
    }
    None => {
    }}
}

fn write_output_to_file(output : std::run::ProcessOutput,
                        filename : &str) {
    if output.status.success() {
        match File::open_mode(&Path::new(filename),
                                std::io::Truncate,
                                std::io::Write)
        {
            Some(mut file) => {
                file.write(output.output);
            }
            None =>{
                println!("Opening {:s} failed!", filename);
            }
        }
    }
    else {
        println!("{:?}", output.error);
    }
}

fn parse_r_redirect(cmd_line : &str) {
    let pair : ~[&str] = cmd_line.rsplit('>').collect();
    let file = pair[0].trim();
    //let command = pair[1].trim();
    match Cmd::new(pair[1].trim()) {
    Some(cmd) => {
        match make_process(cmd, None, None) {
        Some(mut process) => {
            write_output_to_file(
                process.finish_with_output(),
                file);
        }
        None => {
        }}
    }
    None => {
    }}
}

fn rupt(port: Port<Signum>) {
    do spawn {
        loop{
            match port.recv_opt() {
            Some(recv) => {
                match recv {
                Interrupt => {
                    println("Got Interrupt'ed");
                }
                _ => {
                    break;
                }}
            }
            None => {
            }}
        }
    }
    Shell::new("gash > ").run();
}

fn main() {
    let opt_cmd_line = get_cmdline_from_args();

    match opt_cmd_line {
    Some(cmd_line) => {
        let mut shell = Shell::new("");
        shell.run_cmdline(cmd_line);
    }
    None => {
        let mut listener = Listener::new();
        listener.register(Interrupt);
        rupt(listener.port);
    }}
}
<|endoftext|>


<commit_before>/*!
 * Helpers for headers - Tags
 */

/**
 * Spec helpers for header-tags
 */
pub mod spec {
    use storage::file::header::spec::FileHeaderSpec as FHS;
    use module::helpers::spec::{named_text, named_text_array};

    /**
     * helper for a Header spec for
     *
     *  { "URL": "<Text>" }
     */
    pub fn url_key() -> FHS {
        named_text("URL")
    }

    /**
     * helper for a Header spec for
     *
     *  { "TAGS": [ "<Text>", ... ] }
     */
    pub fn tags_key() -> FHS {
        named_text_array("TAGS")
    }

}

/**
 * Data helpers for header-tags
 */
pub mod data {
    use std::ops::Deref;
    use storage::file::header::data::FileHeaderData as FHD;
    use module::Module;
    use clap::ArgMatches;
    use storage::parser::Parser;
    use storage::parser::FileHeaderParser;

    /**
     * Use a Vec<String> to build a Tag-Array:
     *
     *  [ "<Text>", ... ]
     */
    pub fn build_tag_array(tags: Vec<String>) -> FHD {
        let texttags = tags.into_iter().map(|t| FHD::Text(t.clone())).collect();
        FHD::Array { values: Box::new(texttags) }
    }

    /**
     * Fetch tags from a header, whereas the header looks like this:
     *
     *   { ...,
     *     "TAGS": [ "<Text>", ... ],
     *     ...
     *   }
     *
     * Does no spec verification.
     */
    pub fn get_tags_from_header(header: &FHD) -> Vec<String> {
        let tags : Vec<String> = vec![];

        fn match_array(a: &Box<FHD>) -> Vec<String> {
            let mut tags : Vec<String> = vec![];

            match a.deref() {
                &FHD::Array{values: ref vs} => {
                    let values : Vec<FHD> = vs.deref().clone();
                    for value in values {
                        match value {
                            FHD::Text(t) => tags.push(t),
                            _ => warn!("Malformed Header Data: Expected Text, found non-Text"),
                        }
                    }
                }
                _ => warn!("Malformed Header Data: Expected Array, found non-Array"),
            }

            tags
        }

        match header {
            &FHD::Map{keys: ref ks} => {
                let keys : Vec<FHD> = ks.clone();
                for key in keys {
                    match key {
                        FHD::Key{name: ref name, value: ref v} => {
                            if name == "TAGS" {
                                return match_array(v)
                            }
                        },
                        _ => warn!("Malformed Header Data: Expected Key, found non-Key"),
                    }
                }
            },
            _ => warn!("Malformed Header Data: Expected Map, found non-Map"),
        }

        tags
    }

    /**
     * Helper function to alter the tags in a file
     */
    pub fn alter_tags_in_files<HP, F, R>(m: &Module,
                                     matches: &ArgMatches,
                                     parser: &Parser<HP>,
                                     generate_new_tags: F,
                                     rebuild_header: R) -> bool
        where HP: FileHeaderParser,
              F:  Fn(Vec<String>, &Vec<String>) -> Vec<String>,
              R:  Fn(&FHD, Vec<String>) -> Option<FHD>
    {
        use std::process::exit;
        use module::helpers::cli::create_tag_filter;
        use module::helpers::cli::create_hash_filter;
        use module::helpers::cli::create_text_header_field_grep_filter;
        use module::helpers::cli::CliFileFilter;

        let cli_tags = matches.value_of("tags")
                          .map(|ts| {
                            ts.split(",")
                              .map(String::from)
                              .collect::<Vec<String>>()
                          })
                          .unwrap_or(vec![]);

        let filter = {
            let hash_filter = create_hash_filter(matches, "with:id", false);
            let text_filter = create_text_header_field_grep_filter(matches, "with_match", "URL", false);
            let tags_filter = create_tag_filter(matches, "with_tags", false);
            hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter))
        };

        m.runtime()
            .store()
            .load_for_module(m, &parser)
            .into_iter()
            .filter(|file| filter.filter_file(file))
            .map(|file| {
                debug!("Alter tags in file: {:?}", file);

                let hdr = {
                    let f = file.deref().borrow();
                    f.header().clone()
                };

                debug!("Tags:...");
                let old_tags = get_tags_from_header(&hdr);
                debug!("    old_tags = {:?}", &old_tags);
                debug!("    cli_tags = {:?}", &cli_tags);

                let new_tags = generate_new_tags(old_tags, &cli_tags);
                debug!("    new_tags = {:?}", &new_tags);

                let new_header = rebuild_header(&hdr, new_tags)
                    .unwrap_or_else(|| {
                        error!("Could not rebuild header for file");
                        exit(1);
                    });
                {
                    let mut f_mut = file.deref().borrow_mut();
                    f_mut.set_header(new_header);
                }

                m.runtime().store().persist(&parser, file);
                true
            })
            .all(|x| x)
    }

}

<commit_msg>Remove unneeded shorthand field pattern<commit_after>/*!
 * Helpers for headers - Tags
 */

/**
 * Spec helpers for header-tags
 */
pub mod spec {
    use storage::file::header::spec::FileHeaderSpec as FHS;
    use module::helpers::spec::{named_text, named_text_array};

    /**
     * helper for a Header spec for
     *
     *  { "URL": "<Text>" }
     */
    pub fn url_key() -> FHS {
        named_text("URL")
    }

    /**
     * helper for a Header spec for
     *
     *  { "TAGS": [ "<Text>", ... ] }
     */
    pub fn tags_key() -> FHS {
        named_text_array("TAGS")
    }

}

/**
 * Data helpers for header-tags
 */
pub mod data {
    use std::ops::Deref;
    use storage::file::header::data::FileHeaderData as FHD;
    use module::Module;
    use clap::ArgMatches;
    use storage::parser::Parser;
    use storage::parser::FileHeaderParser;

    /**
     * Use a Vec<String> to build a Tag-Array:
     *
     *  [ "<Text>", ... ]
     */
    pub fn build_tag_array(tags: Vec<String>) -> FHD {
        let texttags = tags.into_iter().map(|t| FHD::Text(t.clone())).collect();
        FHD::Array { values: Box::new(texttags) }
    }

    /**
     * Fetch tags from a header, whereas the header looks like this:
     *
     *   { ...,
     *     "TAGS": [ "<Text>", ... ],
     *     ...
     *   }
     *
     * Does no spec verification.
     */
    pub fn get_tags_from_header(header: &FHD) -> Vec<String> {
        let tags : Vec<String> = vec![];

        fn match_array(a: &Box<FHD>) -> Vec<String> {
            let mut tags : Vec<String> = vec![];

            match a.deref() {
                &FHD::Array{values: ref vs} => {
                    let values : Vec<FHD> = vs.deref().clone();
                    for value in values {
                        match value {
                            FHD::Text(t) => tags.push(t),
                            _ => warn!("Malformed Header Data: Expected Text, found non-Text"),
                        }
                    }
                }
                _ => warn!("Malformed Header Data: Expected Array, found non-Array"),
            }

            tags
        }

        match header {
            &FHD::Map{keys: ref ks} => {
                let keys : Vec<FHD> = ks.clone();
                for key in keys {
                    match key {
                        FHD::Key{ref name, value: ref v} => {
                            if name == "TAGS" {
                                return match_array(v)
                            }
                        },
                        _ => warn!("Malformed Header Data: Expected Key, found non-Key"),
                    }
                }
            },
            _ => warn!("Malformed Header Data: Expected Map, found non-Map"),
        }

        tags
    }

    /**
     * Helper function to alter the tags in a file
     */
    pub fn alter_tags_in_files<HP, F, R>(m: &Module,
                                     matches: &ArgMatches,
                                     parser: &Parser<HP>,
                                     generate_new_tags: F,
                                     rebuild_header: R) -> bool
        where HP: FileHeaderParser,
              F:  Fn(Vec<String>, &Vec<String>) -> Vec<String>,
              R:  Fn(&FHD, Vec<String>) -> Option<FHD>
    {
        use std::process::exit;
        use module::helpers::cli::create_tag_filter;
        use module::helpers::cli::create_hash_filter;
        use module::helpers::cli::create_text_header_field_grep_filter;
        use module::helpers::cli::CliFileFilter;

        let cli_tags = matches.value_of("tags")
                          .map(|ts| {
                            ts.split(",")
                              .map(String::from)
                              .collect::<Vec<String>>()
                          })
                          .unwrap_or(vec![]);

        let filter = {
            let hash_filter = create_hash_filter(matches, "with:id", false);
            let text_filter = create_text_header_field_grep_filter(matches, "with_match", "URL", false);
            let tags_filter = create_tag_filter(matches, "with_tags", false);
            hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter))
        };

        m.runtime()
            .store()
            .load_for_module(m, &parser)
            .into_iter()
            .filter(|file| filter.filter_file(file))
            .map(|file| {
                debug!("Alter tags in file: {:?}", file);

                let hdr = {
                    let f = file.deref().borrow();
                    f.header().clone()
                };

                debug!("Tags:...");
                let old_tags = get_tags_from_header(&hdr);
                debug!("    old_tags = {:?}", &old_tags);
                debug!("    cli_tags = {:?}", &cli_tags);

                let new_tags = generate_new_tags(old_tags, &cli_tags);
                debug!("    new_tags = {:?}", &new_tags);

                let new_header = rebuild_header(&hdr, new_tags)
                    .unwrap_or_else(|| {
                        error!("Could not rebuild header for file");
                        exit(1);
                    });
                {
                    let mut f_mut = file.deref().borrow_mut();
                    f_mut.set_header(new_header);
                }

                m.runtime().store().persist(&parser, file);
                true
            })
            .all(|x| x)
    }

}

<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Selecting the default global allocator for Servo

#[global_allocator]
static ALLOC: Allocator = Allocator;

pub use platform::*;

#[cfg(not(windows))]
mod platform {
    extern crate jemalloc_sys as ffi;

    use std::alloc::{GlobalAlloc, Layout};
    use std::os::raw::{c_int, c_void};

    /// Get the size of a heap block.
    pub unsafe extern "C" fn usable_size(ptr: *const c_void) -> usize {
        ffi::malloc_usable_size(ptr as *const _)
    }

    /// Memory allocation APIs compatible with libc
    pub mod libc_compat {
        pub use super::ffi::{malloc, realloc, free};
    }

    pub struct Allocator;

    // The minimum alignment guaranteed by the architecture. This value is used to
    // add fast paths for low alignment values.
    #[cfg(all(any(target_arch = "arm",
                  target_arch = "mips",
                  target_arch = "mipsel",
                  target_arch = "powerpc")))]
    const MIN_ALIGN: usize = 8;
    #[cfg(all(any(target_arch = "x86",
                  target_arch = "x86_64",
                  target_arch = "aarch64",
                  target_arch = "powerpc64",
                  target_arch = "powerpc64le",
                  target_arch = "mips64",
                  target_arch = "s390x",
                  target_arch = "sparc64")))]
    const MIN_ALIGN: usize = 16;

    fn layout_to_flags(align: usize, size: usize) -> c_int {
        // If our alignment is less than the minimum alignment they we may not
        // have to pass special flags asking for a higher alignment. If the
        // alignment is greater than the size, however, then this hits a sort of odd
        // case where we still need to ask for a custom alignment. See #25 for more
        // info.
        if align <= MIN_ALIGN && align <= size {
            0
        } else {
            // Equivalent to the MALLOCX_ALIGN(a) macro.
            align.trailing_zeros() as _
        }
    }

    unsafe impl GlobalAlloc for Allocator {
        #[inline]
        unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
            let flags = layout_to_flags(layout.align(), layout.size());
            ffi::mallocx(layout.size(), flags) as *mut u8
        }

        #[inline]
        unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
            if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() {
                ffi::calloc(1, layout.size()) as *mut u8
            } else {
                let flags = layout_to_flags(layout.align(), layout.size()) | ffi::MALLOCX_ZERO;
                ffi::mallocx(layout.size(), flags) as *mut u8
            }
        }

        #[inline]
        unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
            let flags = layout_to_flags(layout.align(), layout.size());
            ffi::sdallocx(ptr as *mut _, layout.size(), flags)
        }

        #[inline]
        unsafe fn realloc(&self,
                          ptr: *mut u8,
                          layout: Layout,
                          new_size: usize) -> *mut u8 {
            let flags = layout_to_flags(layout.align(), new_size);
            ffi::rallocx(ptr as *mut _, new_size, flags) as *mut u8
        }
    }
}

#[cfg(windows)]
mod platform {
    extern crate kernel32;

    pub use std::alloc::System as Allocator;
    use self::kernel32::{GetProcessHeap, HeapSize, HeapValidate};
    use std::os::raw::c_void;

    /// Get the size of a heap block.
    pub unsafe extern "C" fn usable_size(mut ptr: *const c_void) -> usize {
        let heap = GetProcessHeap();

        if HeapValidate(heap, 0, ptr) == 0 {
            ptr = *(ptr as *const *const c_void).offset(-1);
        }

        HeapSize(heap, 0, ptr) as usize
    }
}
<commit_msg>Format component allocator #21373<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Selecting the default global allocator for Servo

#[global_allocator]
static ALLOC: Allocator = Allocator;

pub use platform::*;

#[cfg(not(windows))]
mod platform {
    extern crate jemalloc_sys as ffi;

    use std::alloc::{GlobalAlloc, Layout};
    use std::os::raw::{c_int, c_void};

    /// Get the size of a heap block.
    pub unsafe extern "C" fn usable_size(ptr: *const c_void) -> usize {
        ffi::malloc_usable_size(ptr as *const _)
    }

    /// Memory allocation APIs compatible with libc
    pub mod libc_compat {
        pub use super::ffi::{malloc, realloc, free};
    }

    pub struct Allocator;

    // The minimum alignment guaranteed by the architecture. This value is used to
    // add fast paths for low alignment values.
    #[cfg(
        all(
            any(
                target_arch = "arm",
                target_arch = "mips",
                target_arch = "mipsel",
                target_arch = "powerpc"
            )
        )
    )]
    const MIN_ALIGN: usize = 8;
    #[cfg(
        all(
            any(
                target_arch = "x86",
                target_arch = "x86_64",
                target_arch = "aarch64",
                target_arch = "powerpc64",
                target_arch = "powerpc64le",
                target_arch = "mips64",
                target_arch = "s390x",
                target_arch = "sparc64"
            )
        )
    )]
    const MIN_ALIGN: usize = 16;

    fn layout_to_flags(align: usize, size: usize) -> c_int {
        // If our alignment is less than the minimum alignment they we may not
        // have to pass special flags asking for a higher alignment. If the
        // alignment is greater than the size, however, then this hits a sort of odd
        // case where we still need to ask for a custom alignment. See #25 for more
        // info.
        if align <= MIN_ALIGN && align <= size {
            0
        } else {
            // Equivalent to the MALLOCX_ALIGN(a) macro.
            align.trailing_zeros() as _
        }
    }

    unsafe impl GlobalAlloc for Allocator {
        #[inline]
        unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
            let flags = layout_to_flags(layout.align(), layout.size());
            ffi::mallocx(layout.size(), flags) as *mut u8
        }

        #[inline]
        unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
            if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() {
                ffi::calloc(1, layout.size()) as *mut u8
            } else {
                let flags = layout_to_flags(layout.align(), layout.size()) | ffi::MALLOCX_ZERO;
                ffi::mallocx(layout.size(), flags) as *mut u8
            }
        }

        #[inline]
        unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
            let flags = layout_to_flags(layout.align(), layout.size());
            ffi::sdallocx(ptr as *mut _, layout.size(), flags)
        }

        #[inline]
        unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
            let flags = layout_to_flags(layout.align(), new_size);
            ffi::rallocx(ptr as *mut _, new_size, flags) as *mut u8
        }
    }
}

#[cfg(windows)]
mod platform {
    extern crate kernel32;

    pub use std::alloc::System as Allocator;
    use self::kernel32::{GetProcessHeap, HeapSize, HeapValidate};
    use std::os::raw::c_void;

    /// Get the size of a heap block.
    pub unsafe extern "C" fn usable_size(mut ptr: *const c_void) -> usize {
        let heap = GetProcessHeap();

        if HeapValidate(heap, 0, ptr) == 0 {
            ptr = *(ptr as *const *const c_void).offset(-1);
        }

        HeapSize(heap, 0, ptr) as usize
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use rustc::lint::Context;
use rustc::middle::{ty, def};

use syntax::ptr::P;
use syntax::{ast, ast_map};
use syntax::ast::{TyPath, Path, AngleBracketedParameters, PathSegment, Ty};
use syntax::attr::mark_used;

/// Matches a type with a provided string, and returns its type parameters if successful
///
/// Try not to use this for types defined in crates you own, use match_lang_ty instead (for lint passes)
pub fn match_ty_unwrap<'a>(ty: &'a Ty, segments: &[&str]) -> Option<&'a [P<Ty>]> {
    match ty.node {
        TyPath(Path {segments: ref seg, ..}, _) => {
            // So ast::Path isn't the full path, just the tokens that were provided.
            // I could muck around with the maps and find the full path
            // however the more efficient way is to simply reverse the iterators and zip them
            // which will compare them in reverse until one of them runs out of segments
            if seg.iter().rev().zip(segments.iter().rev()).all(|(a,b)| a.identifier.as_str() == *b) {
                match seg.as_slice().last() {
                    Some(&PathSegment {parameters: AngleBracketedParameters(ref a), ..}) => {
                        Some(a.types.as_slice())
                    }
                    _ => None
                }
            } else {
                None
            }
        },
        _ => None
    }
}

/// Checks if a type has a #[servo_lang = "str"] attribute
pub fn match_lang_ty(cx: &Context, ty: &Ty, value: &str) -> bool {
    let mut found = false;
    if let TyPath(_, ty_id) = ty.node {
        if let Some(def::DefTy(def_id, _)) = cx.tcx.def_map.borrow().get(&ty_id).cloned() {
            // Iterating through attributes is hard because of cross-crate defs
            for attr in ty::get_attrs(cx.tcx, def_id).iter() {
                if let ast::MetaNameValue(ref name, ref val) = attr.node.value.node {
                    if &**name == "servo_lang" {
                        if let ast::LitStr(ref v, _) = val.node {
                            if &**v == value {
                                mark_used(attr);
                                found = true;
                                break
                            }
                        }
                    }
                }
            }
        };
    }
    found
}

// Determines if a block is in an unsafe context so that an unhelpful
// lint can be aborted.
pub fn unsafe_context(map: &ast_map::Map, id: ast::NodeId) -> bool {
    match map.find(map.get_parent(id)) {
        Some(ast_map::NodeImplItem(itm)) => {
            match *itm {
                ast::MethodImplItem(ref meth) => match meth.node {
                    ast::MethDecl(_, _, _, _, style, _, _, _) => match style {
                        ast::Unsafety::Unsafe => true,
                        _ => false,
                    },
                    _ => false,
                },
                _ => false,
            }
        },
        Some(ast_map::NodeItem(itm)) => {
            match itm.node {
                ast::ItemFn(_, style, _, _, _) => match style {
                    ast::Unsafety::Unsafe => true,
                    _ => false,
                },
                _ => false,
            }
        }
        _ => false // There are probably a couple of other unsafe cases we don't care to lint, those will need to be added.
    }
}
<commit_msg>Cleanup match_lang_ty a bit.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use rustc::lint::Context;
use rustc::middle::{ty, def};

use syntax::ptr::P;
use syntax::{ast, ast_map};
use syntax::ast::{TyPath, Path, AngleBracketedParameters, PathSegment, Ty};
use syntax::attr::mark_used;

/// Matches a type with a provided string, and returns its type parameters if successful
///
/// Try not to use this for types defined in crates you own, use match_lang_ty instead (for lint passes)
pub fn match_ty_unwrap<'a>(ty: &'a Ty, segments: &[&str]) -> Option<&'a [P<Ty>]> {
    match ty.node {
        TyPath(Path {segments: ref seg, ..}, _) => {
            // So ast::Path isn't the full path, just the tokens that were provided.
            // I could muck around with the maps and find the full path
            // however the more efficient way is to simply reverse the iterators and zip them
            // which will compare them in reverse until one of them runs out of segments
            if seg.iter().rev().zip(segments.iter().rev()).all(|(a,b)| a.identifier.as_str() == *b) {
                match seg.as_slice().last() {
                    Some(&PathSegment {parameters: AngleBracketedParameters(ref a), ..}) => {
                        Some(a.types.as_slice())
                    }
                    _ => None
                }
            } else {
                None
            }
        },
        _ => None
    }
}

/// Checks if a type has a #[servo_lang = "str"] attribute
pub fn match_lang_ty(cx: &Context, ty: &Ty, value: &str) -> bool {
    let ty_id = match ty.node {
        TyPath(_, ty_id) => ty_id,
        _ => return false,
    };

    let def_id = match cx.tcx.def_map.borrow().get(&ty_id).cloned() {
        Some(def::DefTy(def_id, _)) => def_id,
        _ => return false,
    };

    ty::get_attrs(cx.tcx, def_id).iter().any(|attr| {
        match attr.node.value.node {
            ast::MetaNameValue(ref name, ref val) if &**name == "servo_lang" => {
                match val.node {
                    ast::LitStr(ref v, _) if &**v == value => {
                        mark_used(attr);
                        true
                    },
                    _ => false,
                }
            }
            _ => false,
        }
    })
}

// Determines if a block is in an unsafe context so that an unhelpful
// lint can be aborted.
pub fn unsafe_context(map: &ast_map::Map, id: ast::NodeId) -> bool {
    match map.find(map.get_parent(id)) {
        Some(ast_map::NodeImplItem(itm)) => {
            match *itm {
                ast::MethodImplItem(ref meth) => match meth.node {
                    ast::MethDecl(_, _, _, _, style, _, _, _) => match style {
                        ast::Unsafety::Unsafe => true,
                        _ => false,
                    },
                    _ => false,
                },
                _ => false,
            }
        },
        Some(ast_map::NodeItem(itm)) => {
            match itm.node {
                ast::ItemFn(_, style, _, _, _) => match style {
                    ast::Unsafety::Unsafe => true,
                    _ => false,
                },
                _ => false,
            }
        }
        _ => false // There are probably a couple of other unsafe cases we don't care to lint, those will need to be added.
    }
}
<|endoftext|>


<commit_before>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{
    counters::{
        PROCESSED_STRUCT_LOG_COUNT, SENT_STRUCT_LOG_COUNT, STRUCT_LOG_PARSE_ERROR_COUNT,
        STRUCT_LOG_QUEUE_ERROR_COUNT, STRUCT_LOG_SEND_ERROR_COUNT,
    },
    logger::Logger,
    struct_log::TcpWriter,
    Event, Filter, Level, Metadata,
};
use chrono::{SecondsFormat, Utc};
use serde::Serialize;
use std::{
    collections::BTreeMap,
    env, fmt,
    io::Write,
    sync::{
        mpsc::{self, Receiver, SyncSender},
        Arc, RwLock,
    },
    thread,
};

const RUST_LOG: &str = "RUST_LOG";
pub const CHANNEL_SIZE: usize = 10000;
const NUM_SEND_RETRIES: u8 = 1;

#[derive(Debug, Serialize)]
pub struct LogEntry {
    #[serde(flatten)]
    metadata: Metadata,
    timestamp: String,
    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
    data: BTreeMap<&'static str, serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    message: Option<String>,
}

impl LogEntry {
    fn new(event: &Event) -> Self {
        use crate::{Key, Value, Visitor};

        struct JsonVisitor<'a>(&'a mut BTreeMap<&'static str, serde_json::Value>);

        impl<'a> Visitor for JsonVisitor<'a> {
            fn visit_pair(&mut self, key: Key, value: Value<'_>) {
                let v = match value {
                    Value::Debug(d) => serde_json::Value::String(format!("{:?}", d)),
                    Value::Display(d) => serde_json::Value::String(d.to_string()),
                    Value::Serde(s) => serde_json::to_value(s).unwrap(),
                };

                self.0.insert(key.as_str(), v);
            }
        }

        let metadata = *event.metadata();
        let message = event.message().map(fmt::format);

        let mut data = BTreeMap::new();
        for schema in event.keys_and_values() {
            schema.visit(&mut JsonVisitor(&mut data));
        }

        Self {
            metadata,
            timestamp: Utc::now().to_rfc3339_opts(SecondsFormat::Micros, true),
            data,
            message,
        }
    }
}

pub struct LibraLoggerBuilder {
    channel_size: usize,
    level: Level,
    address: Option<String>,
    printer: Option<Box<dyn Writer>>,
    is_async: bool,
}

impl LibraLoggerBuilder {
    #[allow(clippy::new_without_default)]
    pub fn new() -> Self {
        Self {
            channel_size: CHANNEL_SIZE,
            level: Level::Info,
            address: None,
            printer: Some(Box::new(StderrWriter)),
            is_async: false,
        }
    }

    pub fn address(&mut self, address: String) -> &mut Self {
        self.address = Some(address);
        self
    }

    pub fn read_env(&mut self) -> &mut Self {
        if let Ok(address) = env::var("STRUCT_LOG_TCP_ADDR") {
            self.address(address);
        }
        self
    }

    pub fn level(&mut self, level: Level) -> &mut Self {
        self.level = level;
        self
    }

    pub fn channel_size(&mut self, channel_size: usize) -> &mut Self {
        self.channel_size = channel_size;
        self
    }

    pub fn printer(&mut self, printer: Box<dyn Writer + Send + Sync + 'static>) -> &mut Self {
        self.printer = Some(printer);
        self
    }

    pub fn is_async(&mut self, is_async: bool) -> &mut Self {
        self.is_async = is_async;
        self
    }

    pub fn init(&mut self) {
        self.build();
    }

    pub fn build(&mut self) -> Arc<LibraLogger> {
        let filter = {
            let mut filter_builder = Filter::builder();

            if env::var(RUST_LOG).is_ok() {
                filter_builder.with_env(RUST_LOG);
            } else {
                filter_builder.filter_level(self.level.into());
            }

            filter_builder.build()
        };

        let logger = if self.is_async {
            let (sender, receiver) = mpsc::sync_channel(self.channel_size);
            let service = LoggerService {
                receiver,
                address: self.address.clone(),
                printer: self.printer.take(),
            };
            let logger = Arc::new(LibraLogger {
                sender: Some(sender),
                printer: None,
                filter: RwLock::new(filter),
            });

            thread::spawn(move || service.run());
            logger
        } else {
            Arc::new(LibraLogger {
                sender: None,
                printer: self.printer.take(),
                filter: RwLock::new(filter),
            })
        };

        crate::logger::set_global_logger(logger.clone());
        logger
    }
}

pub struct LibraLogger {
    sender: Option<SyncSender<LogEntry>>,
    printer: Option<Box<dyn Writer>>,
    filter: RwLock<Filter>,
}

impl LibraLogger {
    pub fn builder() -> LibraLoggerBuilder {
        LibraLoggerBuilder::new()
    }

    #[allow(clippy::new_ret_no_self)]
    pub fn new() -> LibraLoggerBuilder {
        Self::builder()
    }

    pub fn init_for_testing() {
        if env::var(RUST_LOG).is_err() {
            return;
        }

        Self::builder()
            .is_async(false)
            .printer(Box::new(StderrWriter))
            .build();
    }

    pub fn set_filter(&self, filter: Filter) {
        *self.filter.write().unwrap() = filter;
    }

    fn send_entry(&self, entry: LogEntry) {
        if let Some(printer) = &self.printer {
            let s = format(&entry).expect("Unable to format");
            printer.write(s);
        }

        if let Some(sender) = &self.sender {
            if let Err(e) = sender.try_send(entry) {
                STRUCT_LOG_QUEUE_ERROR_COUNT.inc();
                eprintln!("Failed to send structured log: {}", e);
            }
        }
    }
}

impl Logger for LibraLogger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        self.filter.read().unwrap().enabled(metadata)
    }

    fn record(&self, event: &Event) {
        let entry = LogEntry::new(event);

        self.send_entry(entry)
    }
}

struct LoggerService {
    receiver: Receiver<LogEntry>,
    address: Option<String>,
    printer: Option<Box<dyn Writer>>,
}

impl LoggerService {
    pub fn run(mut self) {
        let mut writer = self.address.take().map(TcpWriter::new);

        for entry in self.receiver {
            PROCESSED_STRUCT_LOG_COUNT.inc();

            if let Some(writer) = &mut writer {
                Self::write_to_logstash(writer, &entry);
            }

            if let Some(printer) = &self.printer {
                let s = format(&entry).expect("Unable to format");
                printer.write(s)
            }
        }
    }

    /// Writes a log line into json_lines logstash format, which has a newline at the end
    fn write_to_logstash(stream: &mut TcpWriter, entry: &LogEntry) {
        let message = if let Ok(json) = serde_json::to_string(entry) {
            json
        } else {
            STRUCT_LOG_PARSE_ERROR_COUNT.inc();
            return;
        };

        let message = message + "\n";
        let bytes = message.as_bytes();

        // Attempt to write the log up to NUM_SEND_RETRIES + 1, and then drop it
        // Each `write_all` call will attempt to open a connection if one isn't open
        let mut result = stream.write_all(bytes);
        for _ in 0..NUM_SEND_RETRIES {
            if result.is_ok() {
                break;
            } else {
                result = stream.write_all(bytes);
            }
        }

        if let Err(e) = result {
            STRUCT_LOG_SEND_ERROR_COUNT.inc();
            eprintln!(
                "[Logging] Error while sending data to logstash({}): {}",
                stream.endpoint(),
                e
            );
        } else {
            SENT_STRUCT_LOG_COUNT.inc()
        }
    }
}

/// An trait encapsulating the operations required for writing logs.
pub trait Writer: Send + Sync {
    /// Write the log.
    fn write(&self, log: String);
}

/// A struct for writing logs to stderr
struct StderrWriter;

impl Writer for StderrWriter {
    /// Write log to stderr
    fn write(&self, log: String) {
        eprintln!("{}", log);
    }
}

/// A struct for writing logs to a file
pub struct FileWriter {
    log_file: std::sync::RwLock<std::fs::File>,
}

impl FileWriter {
    pub fn new(log_file: std::path::PathBuf) -> Self {
        let file = std::fs::OpenOptions::new()
            .write(true)
            .create(true)
            .open(log_file)
            .expect("Unable to open log file");
        Self {
            log_file: std::sync::RwLock::new(file),
        }
    }
}

impl Writer for FileWriter {
    /// Write to file
    fn write(&self, log: String) {
        if let Err(err) = writeln!(self.log_file.write().unwrap(), "{}", log) {
            eprintln!("Unable to write to log file: {}", err.to_string());
        }
    }
}

/// Converts a record into a string representation:
/// UNIX_TIMESTAMP LOG_LEVEL FILE:LINE MESSAGE
/// Example:
/// 2020-03-07 05:03:03 INFO common/libra-logger/src/lib.rs:261 Hello
fn format(entry: &LogEntry) -> Result<String, fmt::Error> {
    use std::fmt::Write;

    let mut w = String::new();
    write!(
        w,
        "{} {} {}",
        entry.metadata.level(),
        entry.timestamp,
        entry.metadata.location()
    )?;

    if let Some(message) = &entry.message {
        write!(w, " {}", message)?;
    }

    if !entry.data.is_empty() {
        write!(w, " {}", serde_json::to_string(&entry.data).unwrap())?;
    }

    Ok(w)
}

#[cfg(test)]
mod tests {
    use super::LogEntry;
    use crate::{
        debug, error, info, logger::Logger, trace, warn, Event, Key, KeyValue, Level, Metadata,
        Schema, Value, Visitor,
    };
    use chrono::{DateTime, Utc};
    use serde_json::Value as JsonValue;
    use std::sync::{
        mpsc::{self, Receiver, SyncSender},
        Arc,
    };

    #[derive(serde::Serialize)]
    #[serde(rename_all = "snake_case")]
    enum Enum {
        FooBar,
    }

    struct TestSchema<'a> {
        foo: usize,
        bar: &'a Enum,
    }

    impl Schema for TestSchema<'_> {
        fn visit(&self, visitor: &mut dyn Visitor) {
            visitor.visit_pair(Key::new("foo"), Value::from_serde(&self.foo));
            visitor.visit_pair(Key::new("bar"), Value::from_serde(&self.bar));
        }
    }

    struct LogStream(SyncSender<LogEntry>);

    impl LogStream {
        fn new() -> (Self, Receiver<LogEntry>) {
            let (sender, receiver) = mpsc::sync_channel(1024);
            (Self(sender), receiver)
        }
    }

    impl Logger for LogStream {
        fn enabled(&self, metadata: &Metadata) -> bool {
            metadata.level() <= Level::Debug
        }

        fn record(&self, event: &Event) {
            self.0.send(LogEntry::new(event)).unwrap();
        }
    }

    fn set_test_logger() -> Receiver<LogEntry> {
        let (logger, receiver) = LogStream::new();
        let logger = Arc::new(logger);
        crate::logger::set_global_logger(logger);
        receiver
    }

    // TODO: Find a better mechanism for testing that allows setting the logger not globally
    #[test]
    fn basic() {
        let receiver = set_test_logger();
        let number = 12345;

        // Send an info log
        let before = Utc::now();
        info!(
            TestSchema {
                foo: 5,
                bar: &Enum::FooBar
            },
            test = true,
            category = "name",
            KeyValue::new("display", Value::from_display(&number)),
            "This is a log"
        );
        let after = Utc::now();

        let entry = receiver.recv().unwrap();

        // Ensure standard fields are filled
        assert_eq!(entry.metadata.level(), Level::Info);
        assert_eq!(
            entry.metadata.target(),
            module_path!().split("::").next().unwrap()
        );
        assert_eq!(entry.metadata.module_path(), module_path!());
        assert_eq!(entry.metadata.file(), file!());
        assert_eq!(entry.message.as_deref(), Some("This is a log"));

        // Log time should be the time the structured log entry was created
        let timestamp = DateTime::parse_from_rfc3339(&entry.timestamp).unwrap();
        let timestamp: DateTime<Utc> = DateTime::from(timestamp);
        assert!(before <= timestamp && timestamp <= after);

        // Ensure data stored is the right type
        assert_eq!(entry.data.get("foo").and_then(JsonValue::as_u64), Some(5));
        assert_eq!(
            entry.data.get("bar").and_then(JsonValue::as_str),
            Some("foo_bar")
        );
        assert_eq!(
            entry.data.get("display").and_then(JsonValue::as_str),
            Some(format!("{}", number)).as_deref(),
        );
        assert_eq!(
            entry.data.get("test").and_then(JsonValue::as_bool),
            Some(true),
        );
        assert_eq!(
            entry.data.get("category").and_then(JsonValue::as_str),
            Some("name"),
        );

        // Test all log levels work properly
        // Tracing should be skipped because the Logger was setup to skip Tracing events
        trace!("trace");
        debug!("debug");
        info!("info");
        warn!("warn");
        error!("error");

        let levels = &[Level::Debug, Level::Info, Level::Warn, Level::Error];

        for level in levels {
            let entry = receiver.recv().unwrap();
            assert_eq!(entry.metadata.level(), *level);
        }
    }
}
<commit_msg>logger: capture thread name if available<commit_after>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{
    counters::{
        PROCESSED_STRUCT_LOG_COUNT, SENT_STRUCT_LOG_COUNT, STRUCT_LOG_PARSE_ERROR_COUNT,
        STRUCT_LOG_QUEUE_ERROR_COUNT, STRUCT_LOG_SEND_ERROR_COUNT,
    },
    logger::Logger,
    struct_log::TcpWriter,
    Event, Filter, Level, Metadata,
};
use chrono::{SecondsFormat, Utc};
use serde::Serialize;
use std::{
    collections::BTreeMap,
    env, fmt,
    io::Write,
    sync::{
        mpsc::{self, Receiver, SyncSender},
        Arc, RwLock,
    },
    thread,
};

const RUST_LOG: &str = "RUST_LOG";
pub const CHANNEL_SIZE: usize = 10000;
const NUM_SEND_RETRIES: u8 = 1;

#[derive(Debug, Serialize)]
pub struct LogEntry {
    #[serde(flatten)]
    metadata: Metadata,
    #[serde(skip_serializing_if = "Option::is_none")]
    thread_name: Option<String>,
    timestamp: String,
    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
    data: BTreeMap<&'static str, serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    message: Option<String>,
}

impl LogEntry {
    fn new(event: &Event, thread_name: Option<&str>) -> Self {
        use crate::{Key, Value, Visitor};

        struct JsonVisitor<'a>(&'a mut BTreeMap<&'static str, serde_json::Value>);

        impl<'a> Visitor for JsonVisitor<'a> {
            fn visit_pair(&mut self, key: Key, value: Value<'_>) {
                let v = match value {
                    Value::Debug(d) => serde_json::Value::String(format!("{:?}", d)),
                    Value::Display(d) => serde_json::Value::String(d.to_string()),
                    Value::Serde(s) => serde_json::to_value(s).unwrap(),
                };

                self.0.insert(key.as_str(), v);
            }
        }

        let metadata = *event.metadata();
        let thread_name = thread_name.map(ToOwned::to_owned);
        let message = event.message().map(fmt::format);

        let mut data = BTreeMap::new();
        for schema in event.keys_and_values() {
            schema.visit(&mut JsonVisitor(&mut data));
        }

        Self {
            metadata,
            thread_name,
            timestamp: Utc::now().to_rfc3339_opts(SecondsFormat::Micros, true),
            data,
            message,
        }
    }
}

pub struct LibraLoggerBuilder {
    channel_size: usize,
    level: Level,
    address: Option<String>,
    printer: Option<Box<dyn Writer>>,
    is_async: bool,
}

impl LibraLoggerBuilder {
    #[allow(clippy::new_without_default)]
    pub fn new() -> Self {
        Self {
            channel_size: CHANNEL_SIZE,
            level: Level::Info,
            address: None,
            printer: Some(Box::new(StderrWriter)),
            is_async: false,
        }
    }

    pub fn address(&mut self, address: String) -> &mut Self {
        self.address = Some(address);
        self
    }

    pub fn read_env(&mut self) -> &mut Self {
        if let Ok(address) = env::var("STRUCT_LOG_TCP_ADDR") {
            self.address(address);
        }
        self
    }

    pub fn level(&mut self, level: Level) -> &mut Self {
        self.level = level;
        self
    }

    pub fn channel_size(&mut self, channel_size: usize) -> &mut Self {
        self.channel_size = channel_size;
        self
    }

    pub fn printer(&mut self, printer: Box<dyn Writer + Send + Sync + 'static>) -> &mut Self {
        self.printer = Some(printer);
        self
    }

    pub fn is_async(&mut self, is_async: bool) -> &mut Self {
        self.is_async = is_async;
        self
    }

    pub fn init(&mut self) {
        self.build();
    }

    pub fn build(&mut self) -> Arc<LibraLogger> {
        let filter = {
            let mut filter_builder = Filter::builder();

            if env::var(RUST_LOG).is_ok() {
                filter_builder.with_env(RUST_LOG);
            } else {
                filter_builder.filter_level(self.level.into());
            }

            filter_builder.build()
        };

        let logger = if self.is_async {
            let (sender, receiver) = mpsc::sync_channel(self.channel_size);
            let service = LoggerService {
                receiver,
                address: self.address.clone(),
                printer: self.printer.take(),
            };
            let logger = Arc::new(LibraLogger {
                sender: Some(sender),
                printer: None,
                filter: RwLock::new(filter),
            });

            thread::spawn(move || service.run());
            logger
        } else {
            Arc::new(LibraLogger {
                sender: None,
                printer: self.printer.take(),
                filter: RwLock::new(filter),
            })
        };

        crate::logger::set_global_logger(logger.clone());
        logger
    }
}

pub struct LibraLogger {
    sender: Option<SyncSender<LogEntry>>,
    printer: Option<Box<dyn Writer>>,
    filter: RwLock<Filter>,
}

impl LibraLogger {
    pub fn builder() -> LibraLoggerBuilder {
        LibraLoggerBuilder::new()
    }

    #[allow(clippy::new_ret_no_self)]
    pub fn new() -> LibraLoggerBuilder {
        Self::builder()
    }

    pub fn init_for_testing() {
        if env::var(RUST_LOG).is_err() {
            return;
        }

        Self::builder()
            .is_async(false)
            .printer(Box::new(StderrWriter))
            .build();
    }

    pub fn set_filter(&self, filter: Filter) {
        *self.filter.write().unwrap() = filter;
    }

    fn send_entry(&self, entry: LogEntry) {
        if let Some(printer) = &self.printer {
            let s = format(&entry).expect("Unable to format");
            printer.write(s);
        }

        if let Some(sender) = &self.sender {
            if let Err(e) = sender.try_send(entry) {
                STRUCT_LOG_QUEUE_ERROR_COUNT.inc();
                eprintln!("Failed to send structured log: {}", e);
            }
        }
    }
}

impl Logger for LibraLogger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        self.filter.read().unwrap().enabled(metadata)
    }

    fn record(&self, event: &Event) {
        let entry = LogEntry::new(event, ::std::thread::current().name());

        self.send_entry(entry)
    }
}

struct LoggerService {
    receiver: Receiver<LogEntry>,
    address: Option<String>,
    printer: Option<Box<dyn Writer>>,
}

impl LoggerService {
    pub fn run(mut self) {
        let mut writer = self.address.take().map(TcpWriter::new);

        for entry in self.receiver {
            PROCESSED_STRUCT_LOG_COUNT.inc();

            if let Some(writer) = &mut writer {
                Self::write_to_logstash(writer, &entry);
            }

            if let Some(printer) = &self.printer {
                let s = format(&entry).expect("Unable to format");
                printer.write(s)
            }
        }
    }

    /// Writes a log line into json_lines logstash format, which has a newline at the end
    fn write_to_logstash(stream: &mut TcpWriter, entry: &LogEntry) {
        let message = if let Ok(json) = serde_json::to_string(entry) {
            json
        } else {
            STRUCT_LOG_PARSE_ERROR_COUNT.inc();
            return;
        };

        let message = message + "\n";
        let bytes = message.as_bytes();

        // Attempt to write the log up to NUM_SEND_RETRIES + 1, and then drop it
        // Each `write_all` call will attempt to open a connection if one isn't open
        let mut result = stream.write_all(bytes);
        for _ in 0..NUM_SEND_RETRIES {
            if result.is_ok() {
                break;
            } else {
                result = stream.write_all(bytes);
            }
        }

        if let Err(e) = result {
            STRUCT_LOG_SEND_ERROR_COUNT.inc();
            eprintln!(
                "[Logging] Error while sending data to logstash({}): {}",
                stream.endpoint(),
                e
            );
        } else {
            SENT_STRUCT_LOG_COUNT.inc()
        }
    }
}

/// An trait encapsulating the operations required for writing logs.
pub trait Writer: Send + Sync {
    /// Write the log.
    fn write(&self, log: String);
}

/// A struct for writing logs to stderr
struct StderrWriter;

impl Writer for StderrWriter {
    /// Write log to stderr
    fn write(&self, log: String) {
        eprintln!("{}", log);
    }
}

/// A struct for writing logs to a file
pub struct FileWriter {
    log_file: std::sync::RwLock<std::fs::File>,
}

impl FileWriter {
    pub fn new(log_file: std::path::PathBuf) -> Self {
        let file = std::fs::OpenOptions::new()
            .write(true)
            .create(true)
            .open(log_file)
            .expect("Unable to open log file");
        Self {
            log_file: std::sync::RwLock::new(file),
        }
    }
}

impl Writer for FileWriter {
    /// Write to file
    fn write(&self, log: String) {
        if let Err(err) = writeln!(self.log_file.write().unwrap(), "{}", log) {
            eprintln!("Unable to write to log file: {}", err.to_string());
        }
    }
}

/// Converts a record into a string representation:
/// UNIX_TIMESTAMP LOG_LEVEL FILE:LINE MESSAGE
/// Example:
/// 2020-03-07 05:03:03 INFO common/libra-logger/src/lib.rs:261 Hello
fn format(entry: &LogEntry) -> Result<String, fmt::Error> {
    use std::fmt::Write;

    let mut w = String::new();
    write!(
        w,
        "{} {} {}",
        entry.metadata.level(),
        entry.timestamp,
        entry.metadata.location()
    )?;

    if let Some(message) = &entry.message {
        write!(w, " {}", message)?;
    }

    if !entry.data.is_empty() {
        write!(w, " {}", serde_json::to_string(&entry.data).unwrap())?;
    }

    Ok(w)
}

#[cfg(test)]
mod tests {
    use super::LogEntry;
    use crate::{
        debug, error, info, logger::Logger, trace, warn, Event, Key, KeyValue, Level, Metadata,
        Schema, Value, Visitor,
    };
    use chrono::{DateTime, Utc};
    use serde_json::Value as JsonValue;
    use std::{
        sync::{
            mpsc::{self, Receiver, SyncSender},
            Arc,
        },
        thread,
    };

    #[derive(serde::Serialize)]
    #[serde(rename_all = "snake_case")]
    enum Enum {
        FooBar,
    }

    struct TestSchema<'a> {
        foo: usize,
        bar: &'a Enum,
    }

    impl Schema for TestSchema<'_> {
        fn visit(&self, visitor: &mut dyn Visitor) {
            visitor.visit_pair(Key::new("foo"), Value::from_serde(&self.foo));
            visitor.visit_pair(Key::new("bar"), Value::from_serde(&self.bar));
        }
    }

    struct LogStream(SyncSender<LogEntry>);

    impl LogStream {
        fn new() -> (Self, Receiver<LogEntry>) {
            let (sender, receiver) = mpsc::sync_channel(1024);
            (Self(sender), receiver)
        }
    }

    impl Logger for LogStream {
        fn enabled(&self, metadata: &Metadata) -> bool {
            metadata.level() <= Level::Debug
        }

        fn record(&self, event: &Event) {
            let entry = LogEntry::new(event, ::std::thread::current().name());
            self.0.send(entry).unwrap();
        }
    }

    fn set_test_logger() -> Receiver<LogEntry> {
        let (logger, receiver) = LogStream::new();
        let logger = Arc::new(logger);
        crate::logger::set_global_logger(logger);
        receiver
    }

    // TODO: Find a better mechanism for testing that allows setting the logger not globally
    #[test]
    fn basic() {
        let receiver = set_test_logger();
        let number = 12345;

        // Send an info log
        let before = Utc::now();
        info!(
            TestSchema {
                foo: 5,
                bar: &Enum::FooBar
            },
            test = true,
            category = "name",
            KeyValue::new("display", Value::from_display(&number)),
            "This is a log"
        );
        let after = Utc::now();

        let entry = receiver.recv().unwrap();

        // Ensure standard fields are filled
        assert_eq!(entry.metadata.level(), Level::Info);
        assert_eq!(
            entry.metadata.target(),
            module_path!().split("::").next().unwrap()
        );
        assert_eq!(entry.metadata.module_path(), module_path!());
        assert_eq!(entry.metadata.file(), file!());
        assert_eq!(entry.message.as_deref(), Some("This is a log"));

        // Log time should be the time the structured log entry was created
        let timestamp = DateTime::parse_from_rfc3339(&entry.timestamp).unwrap();
        let timestamp: DateTime<Utc> = DateTime::from(timestamp);
        assert!(before <= timestamp && timestamp <= after);

        // Ensure data stored is the right type
        assert_eq!(entry.data.get("foo").and_then(JsonValue::as_u64), Some(5));
        assert_eq!(
            entry.data.get("bar").and_then(JsonValue::as_str),
            Some("foo_bar")
        );
        assert_eq!(
            entry.data.get("display").and_then(JsonValue::as_str),
            Some(format!("{}", number)).as_deref(),
        );
        assert_eq!(
            entry.data.get("test").and_then(JsonValue::as_bool),
            Some(true),
        );
        assert_eq!(
            entry.data.get("category").and_then(JsonValue::as_str),
            Some("name"),
        );

        // Test all log levels work properly
        // Tracing should be skipped because the Logger was setup to skip Tracing events
        trace!("trace");
        debug!("debug");
        info!("info");
        warn!("warn");
        error!("error");

        let levels = &[Level::Debug, Level::Info, Level::Warn, Level::Error];

        for level in levels {
            let entry = receiver.recv().unwrap();
            assert_eq!(entry.metadata.level(), *level);
        }

        // Verify that the thread name is properly included
        let handler = thread::Builder::new()
            .name("named thread".into())
            .spawn(|| info!("thread"))
            .unwrap();

        handler.join().unwrap();
        let entry = receiver.recv().unwrap();
        assert_eq!(entry.thread_name.as_deref(), Some("named thread"));
    }
}
<|endoftext|>