1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Copyright 2015-2017 Intecture Developers. See the COPYRIGHT file at the
// top-level directory of this distribution and at
// https://intecture.io/COPYRIGHT.
//
// Licensed under the Mozilla Public License 2.0 <LICENSE or
// https://www.tldrlegal.com/l/mpl-2.0>. This file may not be copied,
// modified, or distributed except according to those terms.

//! FFI interface for Command

use ffi_helpers::Leaky;
use host::Host;
use libc::{c_char, uint8_t};
use std::convert;
use std::ffi::CString;
use std::panic::catch_unwind;
use super::{Command, CommandResult};

#[repr(C)]
pub struct Ffi__CommandResult {
    pub exit_code: i32,
    pub stdout: *mut c_char,
    pub stderr: *mut c_char,
}

impl convert::From<CommandResult> for Ffi__CommandResult {
    fn from(result: CommandResult) -> Ffi__CommandResult {
        Ffi__CommandResult {
            exit_code: result.exit_code,
            stdout: CString::new(result.stdout).unwrap().into_raw(),
            stderr: CString::new(result.stderr).unwrap().into_raw(),
        }
    }
}

#[no_mangle]
pub extern "C" fn command_new(cmd_ptr: *const c_char) -> *mut Command {
    let cmd_string = trynull!(ptrtostr!(cmd_ptr, "command string"));
    Box::into_raw(Box::new(Command::new(cmd_string)))
}

#[no_mangle]
pub extern "C" fn command_exec(cmd_ptr: *mut Command, host_ptr: *mut Host) -> *mut Ffi__CommandResult {
    let cmd = Leaky::new(trynull!(readptr!(cmd_ptr, "Command pointer")));
    let mut host = Leaky::new(trynull!(readptr!(host_ptr, "Host pointer")));

    let result = trynull!(cmd.exec(&mut host));
    let ffi_result: Ffi__CommandResult = trynull!(catch_unwind(|| result.into()));

    Box::into_raw(Box::new(ffi_result))
}

#[no_mangle]
pub extern "C" fn command_free(cmd_ptr: *mut Command) -> uint8_t {
    tryrc!(boxptr!(cmd_ptr, "Command pointer"));
    0
}

#[no_mangle]
pub extern "C" fn command_result_free(result_ptr: *mut Ffi__CommandResult) -> uint8_t {
    let result = tryrc!(boxptr!(result_ptr, "CommandResult pointer"));
    if !result.stdout.is_null() {
        unsafe { CString::from_raw(result.stdout) };
    }
    if !result.stderr.is_null() {
        unsafe { CString::from_raw(result.stderr) };
    }
    0
}

#[cfg(test)]
mod tests {
    use command::CommandResult;
    #[cfg(feature = "remote-run")]
    use czmq::{ZMsg, ZSys};
    use error::ERRMSG;
    #[cfg(feature = "remote-run")]
    use host::ffi::host_close;
    use host::Host;
    #[cfg(feature = "remote-run")]
    use std::{str, thread};
    use std::ffi::CStr;
    use std::ffi::CString;
    use std::ptr;
    use super::*;

    #[test]
    fn test_convert_command_result() {
        let result = CommandResult {
            exit_code: 0,
            stdout: "moo".to_string(),
            stderr: "cow".to_string(),
        };
        Ffi__CommandResult::from(result);
    }

    #[test]
    fn test_new() {
        let cmd = readptr!(command_new(CString::new("moo").unwrap().into_raw()), "Command pointer").unwrap();
        assert_eq!(cmd.cmd, "moo");

        assert!(command_new(ptr::null()).is_null());
        assert_eq!(unsafe { CStr::from_ptr(ERRMSG).to_str().unwrap() }, "Received null when we expected a command string pointer");
    }

    #[cfg(feature = "local-run")]
    #[test]
    fn test_exec() {
        let path: Option<String> = None;
        let host = Box::into_raw(Box::new(Host::local(path).unwrap()));

        let whoami = CString::new("whoami").unwrap();
        let cmd = command_new(whoami.into_raw());
        assert!(!cmd.is_null());

        let result = readptr!(command_exec(cmd, host), "CommandResult pointer").unwrap();
        assert_eq!(result.exit_code, 0);

        assert_eq!(command_free(cmd), 0);
        unsafe { Box::from_raw(host) };
    }

    #[cfg(feature = "remote-run")]
    #[test]
    fn test_exec() {
        ZSys::init();

        let (client, mut server) = ZSys::create_pipe().unwrap();
        client.set_sndtimeo(Some(500));
        server.set_rcvtimeo(Some(500));

        let agent_mock = thread::spawn(move || {
            let req = ZMsg::recv(&mut server).unwrap();
            assert_eq!("command::exec", req.popstr().unwrap().unwrap());
            assert_eq!("moo", req.popstr().unwrap().unwrap());

            let rep = ZMsg::new();
            rep.addstr("Ok").unwrap();
            rep.addstr("0").unwrap();
            rep.addstr("cow").unwrap();
            rep.addstr("err").unwrap();
            rep.send(&mut server).unwrap();
        });

        let host = Box::into_raw(Box::new(Host::test_new(None, Some(client), None, None)));
        let whoami = CString::new("moo").unwrap();
        let command = command_new(whoami.into_raw());

        let result = readptr!(command_exec(command, host), "CommandResult pointer").unwrap();
        assert_eq!(result.exit_code, 0);
        assert_eq!(ptrtostr!(result.stdout, "stdout").unwrap(), "cow");
        assert_eq!(ptrtostr!(result.stderr, "stderr").unwrap(), "err");

        assert_eq!(command_free(command), 0);
        assert_eq!(host_close(host), 0);
        agent_mock.join().unwrap();
    }
}