<commit_before>/*!
* gash : main crate
* cs4414 ps2
* Jeremy Letang / free student
*/

#[allow(unused_variable, unused_must_use)];

use std::{io, run, task, str, os};
use std::io::{File, Truncate, Write};
use std::io::buffered::BufferedReader;
use std::run::{Process, ProcessOptions};
use std::libc::c_int;
use std::libc;

use builtins::Builtins;
use error_code::{ErrorCode, Continue, Exit};

mod error_code;
mod builtins;
mod lex;
mod signal;

struct Sh {
    prompt:    ~str,
    builtins:  Builtins,
    chan:      SharedChan<~str>,
    port:      Port<~str>
}

impl Sh {
    pub fn new() -> Sh {
        let (port, chan) = SharedChan::new();
        Sh {
            prompt:     ~"?> ",
            builtins:   Builtins::new(),
            chan:       chan,
            port:       port
        }
    }

    pub fn run(&mut self) {
        let mut stdin = BufferedReader::new(io::stdin());

        loop {
            print!("{}", self.prompt);
            io::stdio::flush();

            let line: ~str = stdin.read_line().expect(format!("Error on file: {} at line {}", file!(), line!()));
            match lex::execute(line) {
                Ok(cmd)     => {
                    if cmd.len() != 0 {
                        if builtins::is_builtin(cmd[0]) {
                            match  self.builtins.execute(cmd) {
                                (out, Exit)     => { print!("{}", out); os::set_exit_status(0); break },
                                (out, _)        => { print!("{}", out) }
                            };
                        } else {
                            self.execute(cmd, 0, 1);
                            self.builtins.save_cmd(line);
                        }
                    }
                },
                Err(err)    => print!("{}", err)
            }

            match self.port.try_recv() {
                Some(t) => print!("{}", t),
                None    => {/* nothing to do */}
            }
        }
    }

    pub fn execute(&mut self, cmd: ~[~str], p_in: c_int, p_out: c_int) {
        if cmd.iter().position(|x| *x == ~"|").is_some() {
            self.pipe(cmd, p_in, p_out)
        } else if cmd.iter().position(|x| *x == ~"<").is_some() {
            self.redirect_left(cmd, p_in, p_out)
        } else if cmd.iter().position(|x| *x == ~">").is_some() {
            self.redirect_right(cmd, p_in, p_out)
        } else {
            self.simple_cmd(cmd, p_in, p_out)
        }

    }

    pub fn pipe(&mut self, cmd: ~[~str], p_in: c_int, p_out: c_int) {
        // (~"Not implemented\n", Continue)
    }

    pub fn redirect_left(&mut self, cmd: ~[~str], p_in: c_int, p_out: c_int) {
         // get the command
        let mut left_it = cmd.iter().take_while(|x| **x != ~"<");
        let mut right_it = cmd.iter().skip_while(|x| **x != ~"<");
        let mut left_cmd: ~[~str] = ~[];
        right_it.next();
        let file = right_it.next().unwrap().clone();
        // get the file
        let file_path  = Path::new(file.clone());
        if file_path.is_dir() {
           println!("-bash: {}: Is a directory", file);
           return;;
        }
        for c in left_it { left_cmd.push(c.clone()); }
        self.execute(left_cmd, fd_from_path(file_path, "r"), p_out);
    }

    pub fn redirect_right(&mut self, cmd: ~[~str], p_in: c_int, p_out: c_int) {
        // get the command
        let mut left_it = cmd.iter().take_while(|x| **x != ~">");
        let mut right_it = cmd.iter().skip_while(|x| **x != ~">");
        let mut left_cmd: ~[~str] = ~[];
        right_it.next();
        let file = right_it.next().unwrap().clone();
        // get the file
        let file_path  = Path::new(file.clone());
        if file_path.is_dir() {
           println!("-bash: {}: Is a directory", file);
           return;;
        }
        for c in left_it { left_cmd.push(c.clone()); }
        self.execute(left_cmd, p_in, fd_from_path(file_path, "w"));
    }

    pub fn simple_cmd(&mut self, mut cmd: ~[~str], p_in: c_int, p_out: c_int) {
            if !cmd_exist(cmd[0]) {
               println!("-gash: {}: command not found", cmd[0]);
               return;
            }
            if *cmd.last_opt().expect(format!("Error on file: {} at line {}", file!(), line!())) == ~"&" {
                let prg = cmd.shift_opt().expect(format!("Error on file: {} at line {}", file!(), line!()));
                cmd.pop();
                let chan = self.chan.clone();
                task::spawn(proc() {
                    let result = match Process::new(prg, cmd, ProcessOptions::new()) {
                        Some(mut pr) => {
                            let out = pr.finish_with_output();
                            let mut r = format!("\n[proc {}] Done: {}\n", pr.get_id() as i32, prg);
                            unsafe {
                                r.push_str(str::raw::from_utf8(out.output).to_owned());
                                r.push_str(str::raw::from_utf8(out.error).to_owned());
                            }
                            r
                        },
                        None => format!("-gash: {}: command not found\n", prg)
                    };

                    chan.send(result)
                });
               print!("")
            } else {
                let prg = cmd.shift_opt().expect(format!("Error on file: {} at line {}", file!(), line!()));
                let mut proc_res = Process::new(prg, cmd,
                    ProcessOptions { env: None, dir: None, in_fd: Some(p_in), out_fd: Some(p_out), err_fd: Some(2)}).unwrap();
                if p_in != 0 {os::close(p_in);}
                if p_out != 1 {os::close(p_out);}
                proc_res.finish();
                // io::stdio::flush();
            }
    }

}

fn cmd_exist(cmd: &str) -> bool {
    run::process_output("which", [cmd.to_owned()]).unwrap().status.success()
}

fn fd_from_path(file_path: Path, args: &str) -> i32 {
    unsafe {
       args.with_c_str(|c_args| {
            libc::fileno(libc::fopen(file_path.to_c_str().unwrap(), c_args))
        })
    }
}

pub fn main() {

    let mut sh = Sh::new();
    sh.run();
}<commit_msg>finalize with pipe<commit_after>/*!
* gash : main crate
* cs4414 ps2
* Jeremy Letang / free student
*/

#[allow(unused_variable)];

use std::{io, run, task, str, os};
use std::io::buffered::BufferedReader;
use std::run::{Process, ProcessOptions};
use std::os::Pipe;
use std::libc::c_int;
use std::libc;

use builtins::Builtins;
use error_code::Exit;

mod error_code;
mod builtins;
mod lex;
mod signal;

struct Sh {
    prompt:    ~str,
    builtins:  Builtins,
    chan:      SharedChan<~str>,
    port:      Port<~str>
}

impl Sh {
    pub fn new() -> Sh {
        let (port, chan) = SharedChan::new();
        Sh {
            prompt:     ~"?> ",
            builtins:   Builtins::new(),
            chan:       chan,
            port:       port
        }
    }

    pub fn run(&mut self) {
        signal::catch();
        let mut stdin = BufferedReader::new(io::stdin());

        loop {
            print!("{}", self.prompt);
            io::stdio::flush();

            let line: ~str = stdin.read_line().expect(format!("Error on file: {} at line {}", file!(), line!()));
            match lex::execute(line) {
                Ok(cmd)     => {
                    if cmd.len() != 0 {
                        if builtins::is_builtin(cmd[0]) {
                            match  self.builtins.execute(cmd) {
                                (out, Exit)     => { print!("{}", out); unsafe {libc::exit(0)} },
                                (out, _)        => { print!("{}", out) }
                            };
                        } else {
                            self.execute(cmd, 0, 1);
                            self.builtins.save_cmd(line);
                        }
                    }
                },
                Err(err)    => print!("{}", err)
            }

            match self.port.try_recv() {
                Some(t) => print!("{}", t),
                None    => {/* nothing to do */}
            }
        }
    }

    pub fn execute(&mut self, cmd: ~[~str], p_in: c_int, p_out: c_int) {
        if cmd.iter().position(|x| *x == ~"|").is_some() {
            self.pipe(cmd, p_in, p_out)
        } else if cmd.iter().position(|x| *x == ~"<").is_some() {
            self.redirect_left(cmd, p_in, p_out)
        } else if cmd.iter().position(|x| *x == ~">").is_some() {
            self.redirect_right(cmd, p_in, p_out)
        } else {
            self.simple_cmd(cmd, p_in, p_out)
        }

    }

    pub fn pipe(&mut self, cmd: ~[~str], p_in: c_int, p_out: c_int) {
        // let cmds: ~[~str] = cmd.split('|').collect();
        let mut cmds: ~[~[~str]] = ~[];
        let mut tmp_cmd: ~[~str] = ~[];

        for c in cmd.move_iter() {
            if c == ~"|" {
                cmds.push(tmp_cmd);
                tmp_cmd = ~[];
            } else {
                tmp_cmd.push(c);
            }
        }
        cmds.push(tmp_cmd);

        let mut pipes: ~[Pipe] = ~[];
        pipes.push(Pipe {input: 0, out: 0});
        for _ in range(0, cmds.len() - 1) { pipes.push(os::pipe()); }
        pipes.push(Pipe {input: 1, out: 1});

        for i in range(0, cmds.len()) {
            self.execute(cmds[i].clone(), pipes[i].input, pipes[i+1].out);
        }
    }

    pub fn redirect_left(&mut self, cmd: ~[~str], p_in: c_int, p_out: c_int) {
         // get the command
        let mut left_it = cmd.iter().take_while(|x| **x != ~"<");
        let mut right_it = cmd.iter().skip_while(|x| **x != ~"<");
        let mut left_cmd: ~[~str] = ~[];
        right_it.next();
        let file = right_it.next().unwrap().clone();
        // get the file
        let file_path  = Path::new(file.clone());
        if file_path.is_dir() {
           println!("-bash: {}: Is a directory", file);
           return;;
        }
        for c in left_it { left_cmd.push(c.clone()); }
        self.execute(left_cmd, fd_from_path(file_path, "r"), p_out);
    }

    pub fn redirect_right(&mut self, cmd: ~[~str], p_in: c_int, p_out: c_int) {
        // get the command
        let mut left_it = cmd.iter().take_while(|x| **x != ~">");
        let mut right_it = cmd.iter().skip_while(|x| **x != ~">");
        let mut left_cmd: ~[~str] = ~[];
        right_it.next();
        let file = right_it.next().unwrap().clone();
        // get the file
        let file_path  = Path::new(file.clone());
        if file_path.is_dir() {
           println!("-bash: {}: Is a directory", file);
           return;;
        }
        for c in left_it { left_cmd.push(c.clone()); }
        self.execute(left_cmd, p_in, fd_from_path(file_path, "w"));
    }

    pub fn simple_cmd(&mut self, mut cmd: ~[~str], p_in: c_int, p_out: c_int) {
            if !cmd_exist(cmd[0]) {
               println!("-gash: {}: command not found", cmd[0]);
               return;
            }
            if *cmd.last_opt().expect(format!("Error on file: {} at line {}", file!(), line!())) == ~"&" {
                let prg = cmd.shift_opt().expect(format!("Error on file: {} at line {}", file!(), line!()));
                cmd.pop();
                let chan = self.chan.clone();
                task::spawn(proc() {
                    let result = match Process::new(prg, cmd, ProcessOptions::new()) {
                        Some(mut pr) => {
                            let out = pr.finish_with_output();
                            let mut r = format!("\n[proc {}] Done: {}\n", pr.get_id() as i32, prg);
                            unsafe {
                                r.push_str(str::raw::from_utf8(out.output).to_owned());
                                r.push_str(str::raw::from_utf8(out.error).to_owned());
                            }
                            r
                        },
                        None => format!("-gash: {}: command not found\n", prg)
                    };

                    chan.send(result)
                });
               print!("")
            } else {
                let prg = cmd.shift_opt().expect(format!("Error on file: {} at line {}", file!(), line!()));
                let mut proc_res = Process::new(prg, cmd,
                    ProcessOptions { env: None, dir: None, in_fd: Some(p_in), out_fd: Some(p_out), err_fd: Some(2)}).unwrap();
                if p_in != 0 {os::close(p_in);}
                if p_out != 1 {os::close(p_out);}
                proc_res.finish();
                // io::stdio::flush();
            }
    }

}

fn cmd_exist(cmd: &str) -> bool {
    run::process_output("which", [cmd.to_owned()]).unwrap().status.success()
}

fn fd_from_path(file_path: Path, args: &str) -> i32 {
    unsafe {
       args.with_c_str(|c_args| {
            libc::fileno(libc::fopen(file_path.to_c_str().unwrap(), c_args))
        })
    }
}

pub fn main() {

    let mut sh = Sh::new();
    sh.run();
}<|endoftext|>


<commit_before>//
// gash.rs
// by Nathan Typanski
//
// 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::io::{stdin, stdio, File, Truncate, Write};
use std::io::process::ProcessExit;
use std::io::signal::{Listener, Interrupt};
use std::path::posix::Path;
use std::option::Option;
use std::task::try;
use std::run::Process;
use std::run::ProcessOptions;
use std::comm::Port;

use extra::getopts;

use std::libc::types::os::arch::posix88::pid_t;
use std::libc::consts::os::posix88::{STDOUT_FILENO, STDIN_FILENO};
use std::libc;

extern {
  pub fn kill(pid: pid_t, sig: libc::c_int) -> libc::c_int;
}

// The basic unit for a command that could be run.
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
        }
    }
}

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

    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)
    }
}

// Background processes are handled differently, but not *that* differently:
// we need to keep track of an ProcessExit port for determining whether a
// process has finished running, as well as a pid for the process (for killing
// it when the shell terminates).
struct BgProcess {
    command      : ~str,
    args         : ~[~str],
    exit_port    : Option<Port<ProcessExit>>,
    pid          : Option<i32>,
    stdin       : Option<i32>,
    stdout      : Option<i32>,
}
impl BgProcess {
    fn new(cmd : Cmd) -> Option<BgProcess> {
        if (cmd_exists(&cmd)) {
            Some(BgProcess {
                command: cmd.program.to_owned(),
                args: cmd.argv.to_owned(),
                exit_port: None,
                pid: None,
                stdin: None,
                stdout: None,
            })
        }
        else { None }
    }

    fn run(&mut self) -> Option<pid_t> {
        // Process exit ports; used for checking dead status.
        let (port, chan): (Port<ProcessExit>, Chan<ProcessExit>) = Chan::new();
        // Process ports; these don't leave this function and are used for
        // sending the PID out in the return value.
        let (pidport, pidchan): (Port<Option<pid_t>>, Chan<Option<pid_t>>)
                            = 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) => {
                    // Send the pid out for the return value
                    pidchan.try_send_deferred(Some(process.get_id()));
                    chan.try_send_deferred(process.finish());
                }
                None => {
                    pidchan.try_send_deferred(None);
                }
            }
        });
        self.exit_port = Some(port);
        self.pid = pidport.recv();
        self.pid
    }
}

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

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

    fn start(&mut self) {
        // Setup the interrupt handler. Has to happen here, or it won't
        // retain control over interrupts.
        let mut listener = Listener::new();
        listener.register(Interrupt);
        let port = listener.port;
        spawn(proc() {
            try(proc() {
                loop {
                    match port.recv_opt() {
                        Some(Interrupt) => {
                        }
                        None => {
                            break;
                        }
                        _ => {
                        }
                    }
                }
                return 0
            });
            return ();
        });

        self.display_prompt();
    }

    fn display_prompt(&mut self) {
        // Standard input reader
        let mut stdin = BufferedReader::new(stdin());
        // Show the prompt
        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.disown_dead();

        // Push commands onto the history
        match program {
            "" => {}
            _  => {
                self.push_hist(cmd_line)
            }
        }

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

    // Nice extra feature: list running jobs.
    fn jobs(&mut self) {
        for cmd in self.processes.iter() {
            print!("{:s}", cmd.command);
            match cmd.pid {
                Some(pid) => {
                    println!(" {:i}", pid);
                }
                None => {
                    println("");
                }
            }
        }
    }

    // Remove dead processes from the list of processes.
    fn disown_dead(&mut self) {
        let mut dead : ~[uint];
        dead = ~[];
        let mut i = 0;
        for cmd in self.processes.iter() {
            borrowed_maybe(&cmd.exit_port, |port| match port.try_recv() {
                Some(_) => {dead.push(i); Some(i)},
                _ => None,
            });
            i += 1;
        }
        for &p in dead.iter() {
            self.processes.remove(p);
        }
    }

    fn kill_all(&mut self) {
        for p in self.processes.iter() {
            match p.pid {
                Some(pid) => {
                    unsafe {
                        // Kill perhaps isn't the best way to do this, but
                        // without it we have no way of killing our background
                        // processes with only the PID - we'd need the process
                        // object itself, and that can't be moved out of the
                        // spawn() that it's trapped inside.
                        kill(pid, 15);
                    }
                }
                None => {
                }
            }
        }
    }

    // Push a new command onto history.
    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);
        }
    }

    // Determine the type of the current block, and send it to the right
    // parsing function.
    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);
        }
    }

    // Parse a new lone process. Background/foreground it appropriately.
    fn parse_process(&mut self, cmd_line : &str) -> Option<Process>{
        maybe(Cmd::new(cmd_line), |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(STDIN_FILENO), Some(STDOUT_FILENO))
            }
        })
    }

    // Parse a process pipeline, and redirect stdin/stdout appropriately.
    fn parse_pipeline(&mut self, cmd_line : &str) {
        let mut pipes : ~[Process] = cmd_line.split('|')
            .filter_map(Cmd::new)
            .filter_map(|c| make_process(c, None, None))
            .to_owned_vec();
        let i = 0;
        let mut p : Process = pipes.pop();
        for mut pipe in pipes.move_iter() {
            self.pipe_input(&mut pipe, &mut p);
        }
    }

    // Redirect the stdout of Process two into the stdin of `cmd`, and return
    // the process created from `cmd`.
    fn pipe_input(&mut self, left : &mut Process, right : &mut Process) -> Process {
        let output = left.finish_with_output();
        if output.status.success() {
            right.input().write(output.output);
            right.close_input();
        }
        right
    }

    // Make a new background process, and push it onto our stack of tracked
    // background processes.
    fn make_bg_process(&mut self, cmd : Cmd) {
        let name = cmd.program.to_owned();
        match BgProcess::new(cmd) {
            Some(mut process) => {
                match process.run() {
                    Some(pid) => {
                        println!("{:s} {:i}", name, pid);
                        self.processes.push(~process);
                    }
                    None => {
                    }
                }
            }
            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;
    }
}

// Determine whether a command exists using "which".
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 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> {
    maybe(FgProcess::new(cmd, stdin, stdout),
          |mut cmdprocess| cmdprocess.run())
}

fn parse_l_redirect(cmd_line : &str) {
    let pair : ~[&str] = cmd_line.rsplit('<').collect();
    let filename = pair[0].trim();
    match maybe(Cmd::new(pair[1].trim()), |c|
                make_process(c, None, Some(STDOUT_FILENO))) {
        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 => {
        }
    }
}

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 cmd = Cmd::new(pair[1].trim());
    match maybe(cmd, |c| make_process(c, None, None)) {
        Some(mut process) => {
            write_output_to_file(
                process.finish_with_output(),
                file);
        }
        None => {
        }
    }
}

// Describes a computation that could fail.
// If v is Some(...), then f is called on v and the result is returned.
// Otherwise, None is returned.
fn maybe<A, B>(v : Option<A>, f : |A| -> Option<B>) -> Option<B> {
    match v {
        Some(v) => f(v),
        None    => None,
    }
}

// Same as maybe(v, f), but for &v.
fn borrowed_maybe<A, B>(v : &Option<A>, f : |&A| -> Option<B>) -> Option<B> {
    match *v {
        Some(ref v) => f(v),
        None    => None,
    }
}

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 shell = Shell::new("gash > ");
            shell.start();
        }
    }
}
<commit_msg>working single-pipe implementation<commit_after>//
// gash.rs
// by Nathan Typanski
//
// 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::io::{stdin, stdio, File, Truncate, Write};
use std::io::process::ProcessExit;
use std::io::signal::{Listener, Interrupt};
use std::path::posix::Path;
use std::option::Option;
use std::task::try;
use std::run::Process;
use std::run::ProcessOptions;
use std::comm::Port;

use extra::getopts;

use std::libc::types::os::arch::posix88::pid_t;
use std::libc::consts::os::posix88::{STDOUT_FILENO, STDIN_FILENO};
use std::libc;

extern {
  pub fn kill(pid: pid_t, sig: libc::c_int) -> libc::c_int;
}

// The basic unit for a command that could be run.
#[deriving(Clone)]
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
        }
    }
}

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

    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)
    }
}

// Background processes are handled differently, but not *that* differently:
// we need to keep track of an ProcessExit port for determining whether a
// process has finished running, as well as a pid for the process (for killing
// it when the shell terminates).
struct BgProcess {
    command      : ~str,
    args         : ~[~str],
    exit_port    : Option<Port<ProcessExit>>,
    pid          : Option<i32>,
    stdin       : Option<i32>,
    stdout      : Option<i32>,
}
impl BgProcess {
    fn new(cmd : Cmd) -> Option<BgProcess> {
        if (cmd_exists(&cmd)) {
            Some(BgProcess {
                command: cmd.program.to_owned(),
                args: cmd.argv.to_owned(),
                exit_port: None,
                pid: None,
                stdin: None,
                stdout: None,
            })
        }
        else { None }
    }

    fn run(&mut self) -> Option<pid_t> {
        // Process exit ports; used for checking dead status.
        let (port, chan): (Port<ProcessExit>, Chan<ProcessExit>) = Chan::new();
        // Process ports; these don't leave this function and are used for
        // sending the PID out in the return value.
        let (pidport, pidchan): (Port<Option<pid_t>>, Chan<Option<pid_t>>)
                            = 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) => {
                    // Send the pid out for the return value
                    pidchan.try_send_deferred(Some(process.get_id()));
                    chan.try_send_deferred(process.finish());
                }
                None => {
                    pidchan.try_send_deferred(None);
                }
            }
        });
        self.exit_port = Some(port);
        self.pid = pidport.recv();
        self.pid
    }
}

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

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

    fn start(&mut self) {
        // Setup the interrupt handler. Has to happen here, or it won't
        // retain control over interrupts.
        let mut listener = Listener::new();
        listener.register(Interrupt);
        let port = listener.port;
        spawn(proc() {
            try(proc() {
                loop {
                    match port.recv_opt() {
                        Some(Interrupt) => {
                        }
                        None => {
                            break;
                        }
                        _ => {
                        }
                    }
                }
                return 0
            });
            return ();
        });

        self.display_prompt();
    }

    fn display_prompt(&mut self) {
        // Standard input reader
        let mut stdin = BufferedReader::new(stdin());
        // Show the prompt
        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.disown_dead();

        // Push commands onto the history
        match program {
            "" => {}
            _  => {
                self.push_hist(cmd_line)
            }
        }

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

    // Nice extra feature: list running jobs.
    fn jobs(&mut self) {
        for cmd in self.processes.iter() {
            print!("{:s}", cmd.command);
            match cmd.pid {
                Some(pid) => {
                    println!(" {:i}", pid);
                }
                None => {
                    println("");
                }
            }
        }
    }

    // Remove dead processes from the list of processes.
    fn disown_dead(&mut self) {
        let mut dead : ~[uint];
        dead = ~[];
        let mut i = 0;
        for cmd in self.processes.iter() {
            borrowed_maybe(&cmd.exit_port, |port| match port.try_recv() {
                Some(_) => {dead.push(i); Some(i)},
                _ => None,
            });
            i += 1;
        }
        for &p in dead.iter() {
            self.processes.remove(p);
        }
    }

    fn kill_all(&mut self) {
        for p in self.processes.iter() {
            match p.pid {
                Some(pid) => {
                    unsafe {
                        // Kill perhaps isn't the best way to do this, but
                        // without it we have no way of killing our background
                        // processes with only the PID - we'd need the process
                        // object itself, and that can't be moved out of the
                        // spawn() that it's trapped inside.
                        kill(pid, 15);
                    }
                }
                None => {
                }
            }
        }
    }

    // Push a new command onto history.
    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);
        }
    }

    // Determine the type of the current block, and send it to the right
    // parsing function.
    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);
        }
    }

    // Parse a new lone process. Background/foreground it appropriately.
    fn parse_process(&mut self, cmd_line : &str) -> Option<Process>{
        maybe(Cmd::new(cmd_line), |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(STDIN_FILENO), Some(STDOUT_FILENO))
            }
        })
    }

    // Parse a process pipeline, and redirect stdin/stdout appropriately.
    fn parse_pipeline(&mut self, cmd_line : &str) {
        let pipes : ~[Cmd] = cmd_line.split('|')
            .filter_map(Cmd::new)
            .to_owned_vec();
        let mut i = 0;
        let mut stdout = stdio::stdout();
        let x = pipes[0].clone();
        let y = pipes[1].clone();
        match self.pipe_input(x, y) {
            Some(mut p) => {
                let output = p.finish_with_output();
                stdout.write(output.output);
            }
            None => {

            }
        }
    }

    // Redirect the stdout of Process two into the stdin of `cmd`, and return
    // the process created from `cmd`.
    fn pipe_input(&mut self, left : Cmd, right : Cmd) -> Option<Process> {
        match simple_process(left) {
            Some(mut left) => {
                match simple_process(right) {
                    Some(mut right) => {
                        let output = left.finish_with_output();
                        if output.status.success() {
                            right.input().write(output.output);
                            right.close_input();
                        }
                        Some(right)
                    }
                    None => {
                        None
                    }
                }
            }
            None => {
                None
            }
        }
    }

    // Make a new background process, and push it onto our stack of tracked
    // background processes.
    fn make_bg_process(&mut self, cmd : Cmd) {
        let name = cmd.program.to_owned();
        match BgProcess::new(cmd) {
            Some(mut process) => {
                match process.run() {
                    Some(pid) => {
                        println!("{:s} {:i}", name, pid);
                        self.processes.push(~process);
                    }
                    None => {
                    }
                }
            }
            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;
    }
}

// Determine whether a command exists using "which".
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 split_words(word : &str) -> ~[~str] {
    word.split(' ').filter_map(
        |x| if x != "" { Some(x.to_owned()) } else { None }
        ).to_owned_vec()
}

fn simple_process(cmd : Cmd) -> Option<run::Process> {
    maybe(FgProcess::new(cmd, None, None),
          |mut cmdprocess| cmdprocess.run())
}

fn make_process(cmd : Cmd,
                stdin: Option<i32>,
                stdout: Option<i32>) -> Option<run::Process> {
    maybe(FgProcess::new(cmd, stdin, stdout),
          |mut cmdprocess| cmdprocess.run())
}

fn parse_l_redirect(cmd_line : &str) {
    let pair : ~[&str] = cmd_line.rsplit('<').collect();
    let filename = pair[0].trim();
    match maybe(Cmd::new(pair[1].trim()), |c|
                make_process(c, None, Some(STDOUT_FILENO))) {
        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 => {
        }
    }
}

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 cmd = Cmd::new(pair[1].trim());
    match maybe(cmd, |c| make_process(c, None, None)) {
        Some(mut process) => {
            write_output_to_file(
                process.finish_with_output(),
                file);
        }
        None => {
        }
    }
}

// Describes a computation that could fail.
// If v is Some(...), then f is called on v and the result is returned.
// Otherwise, None is returned.
fn maybe<A, B>(v : Option<A>, f : |A| -> Option<B>) -> Option<B> {
    match v {
        Some(v) => f(v),
        None    => None,
    }
}

// Same as maybe(v, f), but for &v.
fn borrowed_maybe<A, B>(v : &Option<A>, f : |&A| -> Option<B>) -> Option<B> {
    match *v {
        Some(ref v) => f(v),
        None    => None,
    }
}

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 shell = Shell::new("gash > ");
            shell.start();
        }
    }
}
<|endoftext|>


<commit_before>use ws;
use time;
use std::fmt;
use uuid::Uuid;
use std::sync::{Arc,Mutex};
use std::collections::{HashMap,HashSet};

#[derive(PartialEq, Eq, Hash, Debug, Clone)]
pub enum Item {
    Potatoes,
    Berries,
    TreeSap,
}

trait ParseFrom<T, Out = Self> {
    fn parse (from: T) -> Result<Out, String>;
}

impl ParseFrom<i32> for Item {
    fn parse (i: i32) -> Result<Self, String> {
        match i {
            0 => Ok(Item::Potatoes),
            1 => Ok(Item::Berries),
            2 => Ok(Item::TreeSap),
            _ => Err(format!("Item.parse<i32> failure: unrecognized Item {}", i)),
        }
    }
}

#[derive(Debug)]
#[allow(non_camel_case_types)]
pub enum Action {
    addItemToInventory(Item),
}

#[derive(Debug, Clone)]
pub struct PlayerState {
    inventory: HashSet<Item>,
}

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum Player {
    AnonymousPlayer {
        state : PlayerState,
    },
    RegisteredPlayer {
        id    : i32,
        name  : String,
        state : PlayerState,
    }
}

#[allow(non_snake_case)]
mod AnonymousPlayer {
    use super::*;
    pub fn new () -> Player {
        let mut new_player_inventory = HashSet::new();
        new_player_inventory.insert(Item::Potatoes);

        Player::AnonymousPlayer {
            state: PlayerState {
                inventory: new_player_inventory
            }
        }
    }
}

#[allow(non_snake_case,dead_code)]
mod RegisteredPlayer {
    use super::*;
    pub fn new (id: i32, name: String) -> Player {
        let mut new_player_inventory = HashSet::new();
        new_player_inventory.insert(Item::Potatoes);

        Player::RegisteredPlayer {
            id: id,
            name: name,
            state: PlayerState {
                inventory: new_player_inventory
            }
        }
    }
}

#[derive(Debug, Clone)]
pub struct Connection {
    uuid   : Uuid,
    player : Player,
}

impl Connection {
    fn new(u: Uuid, p: Player) -> Connection {
        Connection {
            uuid  : u,
            player: p
        }
    }
}

impl fmt::Display for Connection {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "conn{{{}}}", self.uuid)
    }
}

fn parse_cookies (req: &ws::Request) -> HashMap<String, String> {
    req.header("cookie")
        .and_then(|cookies_bytes| String::from_utf8(cookies_bytes.to_vec()).ok())
        .unwrap_or(String::from(""))
        .rsplit(";")
        .filter_map(|cookie_string| {
            let mut cookie_pair = cookie_string.split("=");
            match (cookie_pair.next(), cookie_pair.next()) {
                (Some(name), Some(value)) => {
                    Some((String::from(name.trim()), String::from(value.trim())))
                },
                _ => None
            }
        })
        .collect()
}

fn put_cookie (name: String, value: String, resp: &mut ws::Response) {
    let headers = resp.headers_mut();
    let cookie_bytes = format!("{}={}", name, value).as_bytes().to_vec();
    headers.push((String::from("Set-Cookie"), cookie_bytes));
}

fn authorize_ticket_dumb (ticket: Uuid, msg_type: Vec<String>, conn_map: ConnectionMap) -> Result<Vec<String>, String> {
   if conn_map.lock().unwrap().contains_key(&ticket) {
        Ok(msg_type)
   } else {
        Err(String::from("authorize_ticket_dumb authorization failed"))
   }
}

type ConnectionMap = Arc<Mutex<HashMap<Uuid, Connection>>>;

struct WSServer {
    out: ws::Sender,
    connections: ConnectionMap,
}

fn parse_message (msg: ws::Message) -> Result<(Uuid, Vec<String>), String> {
    fn parse_error_msg (reason: &str) -> String {
        format!("parse_message failure: {}", reason)
    }

    msg.into_text()
        .or(Err(parse_error_msg("cannot get text from Message; is it a binary Message?")))
        // NOTE(jordan): feels... wrong. But I hate the nesting! There's no unwrap_or_err()...
        .unwrap_or_else(|err| err)           // Result<String, Err> -> String, or propagate Err
        .split(":")                          // String -> Split<'_, &str>
        .map(|p| p.to_string())              // Split<'_, &str> -> &[String]
        .collect::<Vec<String>>()            // &[String] -> Vec<String>
        .split_first()                       // Vec<String> -> Option<(String, Vec<String>)>
        .ok_or(parse_error_msg("no ticket")) // Option -> Result<(String, &[String]), String>
        .and_then(|(uuid_string, rest)| {    // Result<(String, &[String]), String>
            Uuid::parse_str(uuid_string)     //   |-> Result<(Uuid, Vec<String>), String>
                .or(Err(parse_error_msg("ticket is invalid uuidv4")))
                .map(|uuid| (uuid, Vec::from(rest)))
        })
}

impl ws::Handler for WSServer {
    fn on_request(&mut self, req: &ws::Request) -> ws::Result<ws::Response> {
        let mut resp = ws::Response::from_request(req).unwrap();

        let mut conn_map = self.connections.lock().unwrap();

        println!("ws:req[{}]", time::precise_time_ns());

        let cookies : HashMap<String, String> = parse_cookies(req);

        let mut cookie_existed = true;
        let ticket = cookies.get("bzwf_anon_wstx")
            .and_then(|uuid_string| Uuid::parse_str(uuid_string.as_str()).ok())
            .unwrap_or_else(|| {
                println!("ws:req[{}]: no bzwf_anon_wstx cookie found", time::precise_time_ns());
                cookie_existed = false;
                Uuid::new_v4()
            });

        let users_count = conn_map.len();

        if conn_map.contains_key(&ticket) {
            println!("ws:req[{}]: reconnect: uuid {}", time::precise_time_ns(), ticket);
            println!("{} connected users", users_count);
        } else {
            println!("ws:req[{}]: new connection with uuid {}", time::precise_time_ns(), ticket);

            let cookie_op_str = if cookie_existed {
                "replacing persistence cookie"
            } else {
                "creating persistence cookie"
            };

            println!("ws:req[{}]: {}: bzwf_anon_wstx={}", time::precise_time_ns(), cookie_op_str, ticket);
            println!("{} connected users", users_count + 1);
            put_cookie(String::from("bzwf_anon_wstx"), ticket.to_string(), &mut resp);
            conn_map.insert(ticket, Connection::new(ticket, AnonymousPlayer::new()));
        }

        Ok(resp)
    }

    fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
        println!("ws:rcv[{}]: user with uuid {} sent ws msg {}",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]",
                 msg);

        parse_message(msg)
            .and_then(|(msg_ticket, msg_contents)| {
                println!("authorizing ticket {}", msg_ticket);
                authorize_ticket_dumb(msg_ticket, msg_contents, self.connections.clone())
            })
            .and_then(|msg_contents| {
                // FIXME possible out of bounds error
                let ref msg_type  = msg_contents[0];
                let ref msg_param = msg_contents[1];

                println!("trying to read message contents of {}({})", msg_type, msg_param);

                match (msg_type.as_str(), msg_param) {
                    ("addItemToInventory", item) => {
                        if let Ok(item_num) = item.parse::<i32>() {
                            Item::parse(item_num)
                                .map(|item| Action::addItemToInventory(item))
                        } else {
                            Err(String::from("parse_message failure: addItemToInventory: invalid item code"))
                        }
                    },
                    _ => Err(String::from("parse_message failure: unrecognized message type"))
                }
            })
            .map(|action| {
                self.out.send(format!("gotcha, you want to {:?}", action))
            })
            .unwrap_or_else(|err| {
                println!("{}", err);
                self.out.send("got your message, but not sure what it meant")
            });

        Ok(())
    }

    fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> {
        // TODO
        println!("ws:opn[{}]: user with uuid {} opened ws cxn",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]");
        Ok(())
    }

    fn on_close(&mut self, code: ws::CloseCode, reason: &str) {
        println!("ws:cls[{}]: user with uuid {} closed ws cxn\n\tCode [{:?}] reason: {}",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]",
                 code,
                 reason);
        // TODO
    }

    fn on_error(&mut self, err: ws::Error) {
        println!("ws:err[{}]: user with uuid {} got error {}",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]",
                 err);
        // TODO
    }
}

pub fn server (domain: String, port: i32) -> () {
    let connections : ConnectionMap = Arc::new(Mutex::new(HashMap::new()));
    let addr = format!("{}:{}", domain, port);
    println!("WebSockets server listening on {}", addr);
    ws::listen(addr, |out| WSServer {
        out: out,
        connections: connections.clone(),
    }).unwrap();
}
<commit_msg>Refactor: Found the answer! The `?` (try) operator!<commit_after>use ws;
use time;
use std::fmt;
use uuid::Uuid;
use std::sync::{Arc,Mutex};
use std::collections::{HashMap,HashSet};

#[derive(PartialEq, Eq, Hash, Debug, Clone)]
pub enum Item {
    Potatoes,
    Berries,
    TreeSap,
}

trait ParseFrom<T, Out = Self> {
    fn parse (from: T) -> Result<Out, String>;
}

impl ParseFrom<i32> for Item {
    fn parse (i: i32) -> Result<Self, String> {
        match i {
            0 => Ok(Item::Potatoes),
            1 => Ok(Item::Berries),
            2 => Ok(Item::TreeSap),
            _ => Err(format!("Item.parse<i32> failure: unrecognized Item {}", i)),
        }
    }
}

#[derive(Debug)]
#[allow(non_camel_case_types)]
pub enum Action {
    addItemToInventory(Item),
}

#[derive(Debug, Clone)]
pub struct PlayerState {
    inventory: HashSet<Item>,
}

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum Player {
    AnonymousPlayer {
        state : PlayerState,
    },
    RegisteredPlayer {
        id    : i32,
        name  : String,
        state : PlayerState,
    }
}

#[allow(non_snake_case)]
mod AnonymousPlayer {
    use super::*;
    pub fn new () -> Player {
        let mut new_player_inventory = HashSet::new();
        new_player_inventory.insert(Item::Potatoes);

        Player::AnonymousPlayer {
            state: PlayerState {
                inventory: new_player_inventory
            }
        }
    }
}

#[allow(non_snake_case,dead_code)]
mod RegisteredPlayer {
    use super::*;
    pub fn new (id: i32, name: String) -> Player {
        let mut new_player_inventory = HashSet::new();
        new_player_inventory.insert(Item::Potatoes);

        Player::RegisteredPlayer {
            id: id,
            name: name,
            state: PlayerState {
                inventory: new_player_inventory
            }
        }
    }
}

#[derive(Debug, Clone)]
pub struct Connection {
    uuid   : Uuid,
    player : Player,
}

impl Connection {
    fn new(u: Uuid, p: Player) -> Connection {
        Connection {
            uuid  : u,
            player: p
        }
    }
}

impl fmt::Display for Connection {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "conn{{{}}}", self.uuid)
    }
}

fn parse_cookies (req: &ws::Request) -> HashMap<String, String> {
    req.header("cookie")
        .and_then(|cookies_bytes| String::from_utf8(cookies_bytes.to_vec()).ok())
        .unwrap_or(String::from(""))
        .rsplit(";")
        .filter_map(|cookie_string| {
            let mut cookie_pair = cookie_string.split("=");
            match (cookie_pair.next(), cookie_pair.next()) {
                (Some(name), Some(value)) => {
                    Some((String::from(name.trim()), String::from(value.trim())))
                },
                _ => None
            }
        })
        .collect()
}

fn put_cookie (name: String, value: String, resp: &mut ws::Response) {
    let headers = resp.headers_mut();
    let cookie_bytes = format!("{}={}", name, value).as_bytes().to_vec();
    headers.push((String::from("Set-Cookie"), cookie_bytes));
}

fn authorize_ticket_dumb (ticket: Uuid, msg_type: Vec<String>, conn_map: ConnectionMap) -> Result<Vec<String>, String> {
   if conn_map.lock().unwrap().contains_key(&ticket) {
        Ok(msg_type)
   } else {
        Err(String::from("authorize_ticket_dumb authorization failed"))
   }
}

type ConnectionMap = Arc<Mutex<HashMap<Uuid, Connection>>>;

struct WSServer {
    out: ws::Sender,
    connections: ConnectionMap,
}

fn parse_message (msg: ws::Message) -> Result<(Uuid, Vec<String>), String> {
    fn parse_error_msg (reason: &str) -> String {
        format!("parse_message failure: {}", reason)
    }

    msg.into_text()
        .or(Err(parse_error_msg("cannot get text from Message; is it a binary Message?")))
        ?.split(":")                         // String -> Split<'_, &str>
        .map(|p| p.to_string())              // Split<'_, &str> -> &[String]
        .collect::<Vec<String>>()            // &[String] -> Vec<String>
        .split_first()                       // Vec<String> -> Option<(String, Vec<String>)>
        .ok_or(parse_error_msg("no ticket")) // Option -> Result<(String, &[String]), String>
        .and_then(|(uuid_string, rest)| {    // Result<(String, &[String]), String>
            Uuid::parse_str(uuid_string)     //   |-> Result<(Uuid, Vec<String>), String>
                .or(Err(parse_error_msg("ticket is invalid uuidv4")))
                .map(|uuid| (uuid, Vec::from(rest)))
        })
}

impl ws::Handler for WSServer {
    fn on_request(&mut self, req: &ws::Request) -> ws::Result<ws::Response> {
        let mut resp = ws::Response::from_request(req).unwrap();

        let mut conn_map = self.connections.lock().unwrap();

        println!("ws:req[{}]", time::precise_time_ns());

        let cookies : HashMap<String, String> = parse_cookies(req);

        let mut cookie_existed = true;
        let ticket = cookies.get("bzwf_anon_wstx")
            .and_then(|uuid_string| Uuid::parse_str(uuid_string.as_str()).ok())
            .unwrap_or_else(|| {
                println!("ws:req[{}]: no bzwf_anon_wstx cookie found", time::precise_time_ns());
                cookie_existed = false;
                Uuid::new_v4()
            });

        let users_count = conn_map.len();

        if conn_map.contains_key(&ticket) {
            println!("ws:req[{}]: reconnect: uuid {}", time::precise_time_ns(), ticket);
            println!("{} connected users", users_count);
        } else {
            println!("ws:req[{}]: new connection with uuid {}", time::precise_time_ns(), ticket);

            let cookie_op_str = if cookie_existed {
                "replacing persistence cookie"
            } else {
                "creating persistence cookie"
            };

            println!("ws:req[{}]: {}: bzwf_anon_wstx={}", time::precise_time_ns(), cookie_op_str, ticket);
            println!("{} connected users", users_count + 1);
            put_cookie(String::from("bzwf_anon_wstx"), ticket.to_string(), &mut resp);
            conn_map.insert(ticket, Connection::new(ticket, AnonymousPlayer::new()));
        }

        Ok(resp)
    }

    fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
        println!("ws:rcv[{}]: user with uuid {} sent ws msg {}",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]",
                 msg);

        parse_message(msg)
            .and_then(|(msg_ticket, msg_contents)| {
                println!("authorizing ticket {}", msg_ticket);
                authorize_ticket_dumb(msg_ticket, msg_contents, self.connections.clone())
            })
            .and_then(|msg_contents| {
                // FIXME possible out of bounds error
                let ref msg_type  = msg_contents[0];
                let ref msg_param = msg_contents[1];

                println!("trying to read message contents of {}({})", msg_type, msg_param);

                match (msg_type.as_str(), msg_param) {
                    ("addItemToInventory", item) => {
                        if let Ok(item_num) = item.parse::<i32>() {
                            Item::parse(item_num)
                                .map(|item| Action::addItemToInventory(item))
                        } else {
                            Err(String::from("parse_message failure: addItemToInventory: invalid item code"))
                        }
                    },
                    _ => Err(String::from("parse_message failure: unrecognized message type"))
                }
            })
            .map(|action| {
                self.out.send(format!("gotcha, you want to {:?}", action))
            })
            .unwrap_or_else(|err| {
                println!("{}", err);
                self.out.send("got your message, but not sure what it meant")
            });

        Ok(())
    }

    fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> {
        // TODO
        println!("ws:opn[{}]: user with uuid {} opened ws cxn",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]");
        Ok(())
    }

    fn on_close(&mut self, code: ws::CloseCode, reason: &str) {
        println!("ws:cls[{}]: user with uuid {} closed ws cxn\n\tCode [{:?}] reason: {}",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]",
                 code,
                 reason);
        // TODO
    }

    fn on_error(&mut self, err: ws::Error) {
        println!("ws:err[{}]: user with uuid {} got error {}",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]",
                 err);
        // TODO
    }
}

pub fn server (domain: String, port: i32) -> () {
    let connections : ConnectionMap = Arc::new(Mutex::new(HashMap::new()));
    let addr = format!("{}:{}", domain, port);
    println!("WebSockets server listening on {}", addr);
    ws::listen(addr, |out| WSServer {
        out: out,
        connections: connections.clone(),
    }).unwrap();
}
<|endoftext|>


<commit_before>//! A basic output stream example, using an Output AudioUnit to generate a sine wave.

extern crate coreaudio;

use coreaudio::audio_unit::audio_format::LinearPcmFlags;
use coreaudio::audio_unit::render_callback::{self, data};
use coreaudio::audio_unit::{
    audio_unit_from_device_id, get_default_device_id, set_device_sample_format,
    set_device_sample_rate, RateListener,
};
use coreaudio::audio_unit::{Element, SampleFormat, Scope, StreamFormat};
use coreaudio::sys::kAudioUnitProperty_StreamFormat;
use std::f64::consts::PI;

const SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
// type S = i32; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I32;
// type S = i16; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I16;
// type S = i8; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I8;

const SAMPLE_RATE: f64 = 48000.0;

const INTERLEAVED: bool = true;

struct SineWaveGenerator {
    time: f64,
    /// generated frequency in Hz
    freq: f64,
    /// magnitude of generated signal
    volume: f64,
}

impl SineWaveGenerator {
    fn new(freq: f64, volume: f64) -> Self {
        SineWaveGenerator {
            time: 0.,
            freq,
            volume,
        }
    }
}

impl Iterator for SineWaveGenerator {
    type Item = f32;
    fn next(&mut self) -> Option<f32> {
        self.time += 1. / SAMPLE_RATE;
        let output = ((self.freq * self.time * PI * 2.).sin() * self.volume) as f32;
        Some(output)
    }
}

fn main() -> Result<(), coreaudio::Error> {
    let frequency_hz_l = 1000.;
    let frequency_hz_r = 1200.;
    let volume = 0.95;
    let mut samples_l = SineWaveGenerator::new(frequency_hz_l, volume);
    let mut samples_r = SineWaveGenerator::new(frequency_hz_r, volume);

    // Construct an Output audio unit that delivers audio to the default output device.
    let audio_unit_id = get_default_device_id(false).unwrap();
    let mut audio_unit = audio_unit_from_device_id(audio_unit_id, false)?;

    let mut format_flag = match SAMPLE_FORMAT {
        SampleFormat::F32 => LinearPcmFlags::IS_FLOAT,
        SampleFormat::I32 | SampleFormat::I16 | SampleFormat::I8 => {
            LinearPcmFlags::IS_SIGNED_INTEGER
        }
    };

    if !INTERLEAVED {
        format_flag = format_flag | LinearPcmFlags::IS_NON_INTERLEAVED;
    }

    let stream_format = StreamFormat {
        sample_rate: SAMPLE_RATE,
        sample_format: SAMPLE_FORMAT,
        flags: format_flag | LinearPcmFlags::IS_PACKED,
        // you can change this to 1
        channels: 2,
    };

    println!("stream format={:#?}", &stream_format);
    println!("asbd={:#?}", &stream_format.to_asbd());

    // set the sample rate. This isn't actually needed since the sample rate
    // will anyway be changed when setting the sample format later.
    println!("set device sample rate");
    set_device_sample_rate(audio_unit_id, SAMPLE_RATE)?;

    println!("setting hardware format to i16");
    let hw_format_flag = LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_SIGNED_INTEGER;
    let hw_stream_format = StreamFormat {
        sample_rate: SAMPLE_RATE,
        sample_format: SampleFormat::I16,
        flags: hw_format_flag,
        channels: 2,
    };

    // Note that using a StreamFormat here is convenient, but it only supports a few sample formats.
    // Setting the format to for example 24 bit integers requires using an ASBD.
    set_device_sample_format(audio_unit_id, hw_stream_format.to_asbd())?;

    println!("write audio unit StreamFormat property");
    let id = kAudioUnitProperty_StreamFormat;
    let asbd = stream_format.to_asbd();
    audio_unit.set_property(id, Scope::Input, Element::Output, Some(&asbd))?;

    // For this example, our sine wave expects `f32` data.
    assert!(SampleFormat::F32 == stream_format.sample_format);

    // Register a rate listener
    let mut listener = RateListener::new(audio_unit_id, None);
    listener.register()?;

    if INTERLEAVED {
        println!("Register interleaved callback");
        type Args = render_callback::Args<data::Interleaved<f32>>;
        audio_unit.set_render_callback(move |args| {
            let Args {
                num_frames, data, ..
            } = args;
            println!("frames: {}", num_frames);
            for i in 0..num_frames {
                let sample_l = samples_l.next().unwrap();
                let sample_r = samples_r.next().unwrap();
                data.buffer[2 * i] = sample_l;
                data.buffer[2 * i + 1] = sample_r;
            }
            Ok(())
        })?;
    } else {
        println!("Register non-interleaved callback");
        type Args = render_callback::Args<data::NonInterleaved<f32>>;
        audio_unit.set_render_callback(move |args| {
            let Args {
                num_frames,
                mut data,
                ..
            } = args;
            for i in 0..num_frames {
                let sample_l = samples_l.next().unwrap();
                let sample_r = samples_r.next().unwrap();
                let mut channels = data.channels_mut();
                let left = channels.next().unwrap();
                left[i] = sample_l;
                let right = channels.next().unwrap();
                right[i] = sample_r;
            }
            Ok(())
        })?;
    }
    audio_unit.start()?;

    for _ in 0..100 {
        std::thread::sleep(std::time::Duration::from_millis(100));
        // print all sample change events
        println!("rate events: {:?}", listener.copy_values());
    }
    Ok(())
}
<commit_msg>Add alive listener to sine example<commit_after>//! A basic output stream example, using an Output AudioUnit to generate a sine wave.

extern crate coreaudio;

use coreaudio::audio_unit::audio_format::LinearPcmFlags;
use coreaudio::audio_unit::render_callback::{self, data};
use coreaudio::audio_unit::{
    audio_unit_from_device_id, get_default_device_id, set_device_sample_format,
    set_device_sample_rate, AliveListener, RateListener,
};
use coreaudio::audio_unit::{Element, SampleFormat, Scope, StreamFormat};
use coreaudio::sys::kAudioUnitProperty_StreamFormat;
use std::f64::consts::PI;

const SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
// type S = i32; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I32;
// type S = i16; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I16;
// type S = i8; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I8;

const SAMPLE_RATE: f64 = 48000.0;

const INTERLEAVED: bool = true;

struct SineWaveGenerator {
    time: f64,
    /// generated frequency in Hz
    freq: f64,
    /// magnitude of generated signal
    volume: f64,
}

impl SineWaveGenerator {
    fn new(freq: f64, volume: f64) -> Self {
        SineWaveGenerator {
            time: 0.,
            freq,
            volume,
        }
    }
}

impl Iterator for SineWaveGenerator {
    type Item = f32;
    fn next(&mut self) -> Option<f32> {
        self.time += 1. / SAMPLE_RATE;
        let output = ((self.freq * self.time * PI * 2.).sin() * self.volume) as f32;
        Some(output)
    }
}

fn main() -> Result<(), coreaudio::Error> {
    let frequency_hz_l = 1000.;
    let frequency_hz_r = 1200.;
    let volume = 0.95;
    let mut samples_l = SineWaveGenerator::new(frequency_hz_l, volume);
    let mut samples_r = SineWaveGenerator::new(frequency_hz_r, volume);

    // Construct an Output audio unit that delivers audio to the default output device.
    let audio_unit_id = get_default_device_id(false).unwrap();
    let mut audio_unit = audio_unit_from_device_id(audio_unit_id, false)?;

    let mut format_flag = match SAMPLE_FORMAT {
        SampleFormat::F32 => LinearPcmFlags::IS_FLOAT,
        SampleFormat::I32 | SampleFormat::I16 | SampleFormat::I8 => {
            LinearPcmFlags::IS_SIGNED_INTEGER
        }
    };

    if !INTERLEAVED {
        format_flag = format_flag | LinearPcmFlags::IS_NON_INTERLEAVED;
    }

    let stream_format = StreamFormat {
        sample_rate: SAMPLE_RATE,
        sample_format: SAMPLE_FORMAT,
        flags: format_flag | LinearPcmFlags::IS_PACKED,
        // you can change this to 1
        channels: 2,
    };

    println!("stream format={:#?}", &stream_format);
    println!("asbd={:#?}", &stream_format.to_asbd());

    // set the sample rate. This isn't actually needed since the sample rate
    // will anyway be changed when setting the sample format later.
    println!("set device sample rate");
    set_device_sample_rate(audio_unit_id, SAMPLE_RATE)?;

    println!("setting hardware format to i16");
    let hw_format_flag = LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_SIGNED_INTEGER;
    let hw_stream_format = StreamFormat {
        sample_rate: SAMPLE_RATE,
        sample_format: SampleFormat::I16,
        flags: hw_format_flag,
        channels: 2,
    };

    // Note that using a StreamFormat here is convenient, but it only supports a few sample formats.
    // Setting the format to for example 24 bit integers requires using an ASBD.
    set_device_sample_format(audio_unit_id, hw_stream_format.to_asbd())?;

    println!("write audio unit StreamFormat property");
    let id = kAudioUnitProperty_StreamFormat;
    let asbd = stream_format.to_asbd();
    audio_unit.set_property(id, Scope::Input, Element::Output, Some(&asbd))?;

    // For this example, our sine wave expects `f32` data.
    assert!(SampleFormat::F32 == stream_format.sample_format);

    // Register rate and alive listeners
    let mut rate_listener = RateListener::new(audio_unit_id, None);
    rate_listener.register()?;
    let mut alive_listener = AliveListener::new(audio_unit_id);
    alive_listener.register()?;

    if INTERLEAVED {
        println!("Register interleaved callback");
        type Args = render_callback::Args<data::Interleaved<f32>>;
        audio_unit.set_render_callback(move |args| {
            let Args {
                num_frames, data, ..
            } = args;
            println!("frames: {}", num_frames);
            for i in 0..num_frames {
                let sample_l = samples_l.next().unwrap();
                let sample_r = samples_r.next().unwrap();
                data.buffer[2 * i] = sample_l;
                data.buffer[2 * i + 1] = sample_r;
            }
            Ok(())
        })?;
    } else {
        println!("Register non-interleaved callback");
        type Args = render_callback::Args<data::NonInterleaved<f32>>;
        audio_unit.set_render_callback(move |args| {
            let Args {
                num_frames,
                mut data,
                ..
            } = args;
            for i in 0..num_frames {
                let sample_l = samples_l.next().unwrap();
                let sample_r = samples_r.next().unwrap();
                let mut channels = data.channels_mut();
                let left = channels.next().unwrap();
                left[i] = sample_l;
                let right = channels.next().unwrap();
                right[i] = sample_r;
            }
            Ok(())
        })?;
    }
    audio_unit.start()?;

    for _ in 0..100 {
        std::thread::sleep(std::time::Duration::from_millis(100));
        // print all sample change events
        println!("rate events: {:?}", rate_listener.copy_values());
        println!("alive state: {}", alive_listener.is_alive());
    }
    Ok(())
}
<|endoftext|>


<commit_before>use super::*;

use std::mem;

pub fn with_cstr<R, F>(s: &str, f: F) -> R
where
    F: FnOnce(*const i8) -> R,
{
    let allocated;
    let mut buffer: [u8; 256] = unsafe { mem::uninitialized() };
    let ptr: *const i8 = if s.len() < buffer.len() {
        buffer[0..s.len()].clone_from_slice(s.as_bytes());
        buffer[s.len()] = 0;
        buffer.as_ptr() as *const i8
    } else {
        allocated = Some(CString::new(s).unwrap());
        allocated.as_ref().unwrap().as_ptr()
    };
    f(ptr)
}

pub fn with_opt_cstr<R, F>(s: Option<&str>, f: F) -> R
where
    F: FnOnce(*const i8) -> R,
{
    match s {
        Some(s) => with_cstr(s, f),
        None => f(ptr::null()),
    }
}

pub fn get_cstring<F>(f: F) -> CString
where
    F: Fn(*mut c_char, usize) -> usize,
{
    // Some SB API return the required size of the full string (SBThread::GetStopDescription()),
    // while others return the number of bytes actually written into the buffer (SBPath::GetPath()).
    // In the latter case we have to increase buffer capacity in a loop until the string fits.
    // There also seems to be lack of consensus on whether the terminating NUL should be included in the count or not...

    let buffer: [u8; 1024] = unsafe { mem::uninitialized() };
    let c_ptr = buffer.as_ptr() as *mut c_char;
    let size = f(c_ptr, buffer.len());
    assert!((size as isize) >= 0);
    if size < buffer.len() - 1 {
        // Must have at least 1 unused byte to be sure that we've received the whole string
        unsafe {
            return CStr::from_ptr(c_ptr).to_owned();
        }
    }

    let size_is_reliable = size > buffer.len();
    let capacity = if size_is_reliable {
        size + 1
    } else {
        buffer.len() * 2
    };
    let mut buffer = Vec::with_capacity(capacity);
    loop {
        let c_ptr = buffer.as_ptr() as *mut c_char;
        let size = f(c_ptr, buffer.capacity());
        assert!((size as isize) >= 0);
        if size < buffer.capacity() - 1 || size_is_reliable {
            assert!(size < buffer.capacity()); // should be true if size_is_reliable
            unsafe {
                let s = CStr::from_ptr(c_ptr); // Count bytes to NUL
                buffer.set_len(s.to_bytes().len());
                return CString::from_vec_unchecked(buffer);
            };
        }
        let capacity = buffer.capacity() * 2;
        buffer.reserve(capacity);
    }
}

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

    fn store_as_cstr(s: &str, buff: *mut c_char, size: usize) -> usize {
        let b = unsafe { slice::from_raw_parts_mut(buff as *mut u8, size) };
        let s = s.as_bytes();
        if b.len() > s.len() {
            b[..s.len()].clone_from_slice(s);
            b[s.len()] = 0;
            s.len()
        } else {
            let max = b.len() - 1;
            b[..max].clone_from_slice(&s[..max]);
            b[max] = 0;
            max
        }
    }

    #[test]
    fn test_get_cstring() {
        use std::cell::RefCell;
        for n in 0..200 {
            let string = "0123456789ABC".repeat(n);
            let cstring = CString::new(string.clone()).unwrap();

            let iters = RefCell::new(0..2);
            assert_eq!(
                cstring,
                get_cstring(|buff, size| {
                    assert!(iters.borrow_mut().next().is_some());
                    store_as_cstr(&string, buff, size);
                    // Returns the required storage length
                    string.len()
                })
            );
            let iters = RefCell::new(0..2);
            assert_eq!(
                cstring,
                get_cstring(|buff, size| {
                    assert!(iters.borrow_mut().next().is_some());
                    store_as_cstr(&string, buff, size);
                    // Returns the required storage length, including NUL
                    string.len() + 1
                })
            );
            let iters = RefCell::new(0..100);
            assert_eq!(
                cstring,
                get_cstring(|buff, size| {
                    assert!(iters.borrow_mut().next().is_some());
                    // Returns stored length
                    store_as_cstr(&string, buff, size)
                })
            );
            let iters = RefCell::new(0..100);
            assert_eq!(
                cstring,
                get_cstring(|buff, size| {
                    assert!(iters.borrow_mut().next().is_some());
                    // Returns stored length, including NUL
                    store_as_cstr(&string, buff, size) + 1
                })
            );
        }
    }
}
<commit_msg>Do not assume that char is signed on all platforms.<commit_after>use super::*;

use std::mem;

pub fn with_cstr<R, F>(s: &str, f: F) -> R
where
    F: FnOnce(*const c_char) -> R,
{
    let allocated;
    let mut buffer: [u8; 256] = unsafe { mem::uninitialized() };
    let ptr: *const c_char = if s.len() < buffer.len() {
        buffer[0..s.len()].clone_from_slice(s.as_bytes());
        buffer[s.len()] = 0;
        buffer.as_ptr() as *const c_char
    } else {
        allocated = Some(CString::new(s).unwrap());
        allocated.as_ref().unwrap().as_ptr()
    };
    f(ptr)
}

pub fn with_opt_cstr<R, F>(s: Option<&str>, f: F) -> R
where
    F: FnOnce(*const c_char) -> R,
{
    match s {
        Some(s) => with_cstr(s, f),
        None => f(ptr::null()),
    }
}

pub fn get_cstring<F>(f: F) -> CString
where
    F: Fn(*mut c_char, usize) -> usize,
{
    // Some SB API return the required size of the full string (SBThread::GetStopDescription()),
    // while others return the number of bytes actually written into the buffer (SBPath::GetPath()).
    // In the latter case we have to increase buffer capacity in a loop until the string fits.
    // There also seems to be lack of consensus on whether the terminating NUL should be included in the count or not...

    let buffer: [u8; 1024] = unsafe { mem::uninitialized() };
    let c_ptr = buffer.as_ptr() as *mut c_char;
    let size = f(c_ptr, buffer.len());
    assert!((size as isize) >= 0);
    if size < buffer.len() - 1 {
        // Must have at least 1 unused byte to be sure that we've received the whole string
        unsafe {
            return CStr::from_ptr(c_ptr).to_owned();
        }
    }

    let size_is_reliable = size > buffer.len();
    let capacity = if size_is_reliable {
        size + 1
    } else {
        buffer.len() * 2
    };
    let mut buffer = Vec::with_capacity(capacity);
    loop {
        let c_ptr = buffer.as_ptr() as *mut c_char;
        let size = f(c_ptr, buffer.capacity());
        assert!((size as isize) >= 0);
        if size < buffer.capacity() - 1 || size_is_reliable {
            assert!(size < buffer.capacity()); // should be true if size_is_reliable
            unsafe {
                let s = CStr::from_ptr(c_ptr); // Count bytes to NUL
                buffer.set_len(s.to_bytes().len());
                return CString::from_vec_unchecked(buffer);
            };
        }
        let capacity = buffer.capacity() * 2;
        buffer.reserve(capacity);
    }
}

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

    fn store_as_cstr(s: &str, buff: *mut c_char, size: usize) -> usize {
        let b = unsafe { slice::from_raw_parts_mut(buff as *mut u8, size) };
        let s = s.as_bytes();
        if b.len() > s.len() {
            b[..s.len()].clone_from_slice(s);
            b[s.len()] = 0;
            s.len()
        } else {
            let max = b.len() - 1;
            b[..max].clone_from_slice(&s[..max]);
            b[max] = 0;
            max
        }
    }

    #[test]
    fn test_get_cstring() {
        use std::cell::RefCell;
        for n in 0..200 {
            let string = "0123456789ABC".repeat(n);
            let cstring = CString::new(string.clone()).unwrap();

            let iters = RefCell::new(0..2);
            assert_eq!(
                cstring,
                get_cstring(|buff, size| {
                    assert!(iters.borrow_mut().next().is_some());
                    store_as_cstr(&string, buff, size);
                    // Returns the required storage length
                    string.len()
                })
            );
            let iters = RefCell::new(0..2);
            assert_eq!(
                cstring,
                get_cstring(|buff, size| {
                    assert!(iters.borrow_mut().next().is_some());
                    store_as_cstr(&string, buff, size);
                    // Returns the required storage length, including NUL
                    string.len() + 1
                })
            );
            let iters = RefCell::new(0..100);
            assert_eq!(
                cstring,
                get_cstring(|buff, size| {
                    assert!(iters.borrow_mut().next().is_some());
                    // Returns stored length
                    store_as_cstr(&string, buff, size)
                })
            );
            let iters = RefCell::new(0..100);
            assert_eq!(
                cstring,
                get_cstring(|buff, size| {
                    assert!(iters.borrow_mut().next().is_some());
                    // Returns stored length, including NUL
                    store_as_cstr(&string, buff, size) + 1
                })
            );
        }
    }
}
<|endoftext|>