当ruby成功时,IRB无法运行ruby gem函数

时间:2016-10-22 21:50:47

标签: c ruby malloc

我最近制作了以下ruby gem(称为sse):

#include <ruby.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <signal.h>

/*
 * Module handle
*/

static VALUE sse_module;

/*
 * All exceptions for this module
*/

static VALUE sse_FileNotFoundError;
static VALUE sse_ForkFailureError;
static VALUE sse_PipeFailureError;
static VALUE sse_WriteFailureError;
static VALUE sse_ReadFailureError;
static VALUE sse_WaitpidFailureError;
static VALUE sse_TimeoutError;

/*
 * General utilities
*/

char **split(char *str, char *sep) {
    char *arg = strtok(str, sep);
    size_t arg_list_size = 1;
    char **arg_list = malloc(sizeof(char*));

    arg_list[0] = malloc(strlen(arg));
    strncpy(arg_list[0], arg, strlen(arg));

    while((arg = strtok(NULL, sep)) != NULL) {
        arg_list = realloc(arg_list, (sizeof(char*))*(arg_list_size+1));
        arg_list[arg_list_size] = malloc(strlen(arg));
        strncpy(arg_list[arg_list_size], arg, strlen(arg));
        ++arg_list_size;
    }

    arg_list = realloc(arg_list, (sizeof(char*))*(arg_list_size));
    arg_list[arg_list_size] = malloc(sizeof(char*));
    arg_list[arg_list_size] = (char*)NULL;

    return arg_list;
}

/*
 * Ruby classes and functions
*/

VALUE rb_execute_sh(VALUE self, VALUE rb_stdin, VALUE rb_timeout, VALUE rb_command) {
    int stdin_ends[2];
    int stdout_ends[2];
    int stderr_ends[2];

    if(pipe(stdin_ends) == -1 || pipe(stdout_ends) == -1 || pipe(stderr_ends) == -1) {
        rb_raise(sse_PipeFailureError, "%s:%d (%d) pipe: %s\n", __FILE__, __LINE__, errno, strerror(errno));
    }

    char **command_list = split(StringValueCStr(rb_command), (char*)" ");
    char *stdin_buf = StringValueCStr(rb_stdin);
    pid_t pid = fork();

    if(pid > 0) {
        size_t stdout_buf_size = 0;
        size_t stderr_buf_size = 0;
        size_t stdout_buf_max = 1;
        size_t stderr_buf_max = 1;
        char *stdout_buf = malloc(1);
        char *stderr_buf = malloc(1);

        stdout_buf[0] = '\0';
        stderr_buf[0] = '\0';

        time_t start = time(NULL);
        time_t timeout = NUM2INT(rb_timeout);
        int sent_stdin = 0;

        close(stdin_ends[0]);
        close(stdout_ends[1]);
        close(stderr_ends[1]);

        while(difftime(time(NULL), start) < timeout) {
            int status = waitpid(pid, NULL, WNOHANG);

            if(status == -1) {
                rb_raise(sse_WaitpidFailureError, "%s:%d (%d) waitpid: %s\n", __FILE__, __LINE__, errno, strerror(errno));
            } else if(status == 0) {
                fd_set write_set;
                fd_set read_set;
                struct timeval select_timeout = { 0, 0 };
                int maxfd = (stdin_ends[1] > stdout_ends[0] ? (stdin_ends[1] > stderr_ends[0] ? stdin_ends[1] : stderr_ends[0]) : (stdout_ends[0] > stderr_ends[0] ? stdout_ends[0] : stderr_ends[0]));

                FD_ZERO(&write_set);
                FD_ZERO(&read_set);

                if(sent_stdin == 0) {
                    FD_SET(stdin_ends[1], &write_set);
                }

                FD_SET(stdout_ends[0], &read_set);
                FD_SET(stderr_ends[0], &read_set);

                select(maxfd+1, &read_set, &write_set, NULL, &select_timeout);

                if(FD_ISSET(stdin_ends[1], &write_set)) {
                    sent_stdin = 1;

                    if(write(stdin_ends[1], stdin_buf, strlen(stdin_buf)) == -1) {
                        rb_raise(sse_WriteFailureError, "%s:%d (%d) write: %s\n", __FILE__, __LINE__, errno, strerror(errno));
                    }

                    close(stdin_ends[1]);
                }

                if(FD_ISSET(stdout_ends[0], &read_set)) {
                    char tmp_buf[251];
                    ssize_t bytes_read = read(stdout_ends[0], tmp_buf, 250);

                    if(bytes_read == -1) {
                        rb_raise(sse_ReadFailureError, "%s:%d (%d) read: %s\n", __FILE__, __LINE__, errno, strerror(errno));
                    }

                    tmp_buf[bytes_read] = '\0';

                    while((stdout_buf_size + bytes_read+1) >= stdout_buf_max) {
                        stdout_buf_max *= 2;
                        stdout_buf = realloc(stdout_buf, stdout_buf_max);
                    }

                    strncpy(stdout_buf, tmp_buf, bytes_read);
                    stdout_buf_size += bytes_read;
                    stdout_buf[stdout_buf_size] = '\0';
                }

                if(FD_ISSET(stderr_ends[0], &read_set)) {
                    char tmp_buf[251];
                    ssize_t bytes_read = read(stderr_ends[0], tmp_buf, 250);

                    if(bytes_read == -1) {
                        rb_raise(sse_ReadFailureError, "%s:%d (%d) read: %s\n", __FILE__, __LINE__, errno, strerror(errno));
                    }

                    tmp_buf[bytes_read] = '\0';

                    while((stderr_buf_size + bytes_read+1) >= stderr_buf_max) {
                        stderr_buf_max *= 2;
                        stderr_buf = realloc(stderr_buf, stderr_buf_max);
                    }

                    strncpy(stderr_buf, tmp_buf, bytes_read);
                    stderr_buf_size += bytes_read;
                    stderr_buf[stderr_buf_size] = '\0';
                }
            } else {
                close(stdout_ends[0]);
                close(stderr_ends[1]);
                VALUE result = rb_ary_new2(3);
                rb_ary_store(result, 0, INT2NUM(difftime(time(NULL), start)));
                rb_ary_store(result, 1, rb_str_new_cstr(stdout_buf));
                rb_ary_store(result, 2, rb_str_new_cstr(stderr_buf));
                return result;
            }
        }

        kill(SIGINT, pid);
        rb_raise(sse_TimeoutError, "Proccess took too long to finish.\n");
    } else if(pid == 0) {
        dup2(stdin_ends[0], STDIN_FILENO);
        dup2(stdout_ends[1], STDOUT_FILENO);
        dup2(stderr_ends[1], STDERR_FILENO);
        close(stdin_ends[1]);
        close(stdout_ends[0]);
        close(stderr_ends[0]);

        if(execvp(command_list[0], command_list) == -1) {
            rb_raise(sse_ForkFailureError, "%s:%d (%d) execvp: %s\n", __FILE__, __LINE__, errno, strerror(errno));
        }
    } else {
        rb_raise(sse_FileNotFoundError, "%s:%d (%d) execvp: %s\n", __FILE__, __LINE__, errno, strerror(errno));
    }

    VALUE thunderfury_blessed_blade_of_the_windseeker = rb_ary_new2(3);
    rb_ary_store(thunderfury_blessed_blade_of_the_windseeker, 0, INT2NUM(42));
    rb_ary_store(thunderfury_blessed_blade_of_the_windseeker, 1, rb_str_new_cstr("this is stdout"));
    rb_ary_store(thunderfury_blessed_blade_of_the_windseeker, 2, rb_str_new_cstr("this is stderr"));
    return thunderfury_blessed_blade_of_the_windseeker;
}

/*
 * Initialize the module
*/

void Init_sse() {
    sse_module = rb_define_module("sse");

    sse_FileNotFoundError = rb_define_class_under(sse_module, "FileNotFoundError", rb_eStandardError);
    sse_ForkFailureError = rb_define_class_under(sse_module, "ForkFailureError", rb_eStandardError);
    sse_PipeFailureError = rb_define_class_under(sse_module, "PipeFailureError", rb_eStandardError);
    sse_WriteFailureError = rb_define_class_under(sse_module, "WriteFailureError", rb_eStandardError);
    sse_ReadFailureError = rb_define_class_under(sse_module, "ReadFailureError", rb_eStandardError);
    sse_WaitpidFailureError = rb_define_class_under(sse_module, "WaitpidFailureError", rb_eStandardError);
    sse_TimeoutError = rb_define_class_under(sse_module, "TimeoutError", rb_eStandardError);

    rb_define_global_function("execute_sh", rb_execute_sh, 3);
}

从脚本运行它时效果很好:

admins-MacBook-Pro-2:sse-ruby nchambers$ cat client.rb
require './sse'
result = execute_sh('', 10, 'hostname')
puts "=>#{result[0]}<=\n=>#{result[1]}<=\n=>#{result[2]}<=\n"
admins-MacBook-Pro-2:sse-ruby nchambers$ ruby client.rb
=>0<=
=>admins-MacBook-Pro-2.local
<=
=><=
admins-MacBook-Pro-2:sse-ruby nchambers$

但是,如果我从irb做同样的事情,我会得到这个:

admins-MacBook-Pro-2:sse-ruby nchambers$ irb
2.2.4 :001 > require './sse'
 => true
2.2.4 :002 > result = execute_sh('', 10, 'hostname')
 => [0, "\n.2.4 :003 > :in `execute_sh'\n\tfrom (irb):2\n\tfrom /Users/nchambers/.rvm/rubies/ruby-2.2.4/bin/irb:11:in `<main>'\n", "irb(6804,0x7fffa88db3c0) malloc: *** error for object 0x7ff9ece81880: pointer being freed was not allocated\n*** set a breakpoint in malloc_error_break to debug\n"]
2.2.4 :003 > puts "=>#{result[0]}<=\n=>#{result[1]}<=\n=>#{result[2]}<=\n"
=>0<=
=>
.2.4 :003 > :in `execute_sh'
    from (irb):2
    from /Users/nchambers/.rvm/rubies/ruby-2.2.4/bin/irb:11:in `<main>'
<=
=>irb(6804,0x7fffa88db3c0) malloc: *** error for object 0x7ff9ece81880: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
<=
 => nil
2.2.4 :004 >

我无法理解为什么会发生这种情况。所以我的问题是,为什么IRB在脚本成功的地方失败了?谢谢!

1 个答案:

答案 0 :(得分:-1)

arg_list[0] = malloc(strlen(arg));
strncpy(arg_list[0], arg, strlen(arg));
arg_list[arg_list_size] = malloc(strlen(arg));
strncpy(arg_list[arg_list_size], arg, strlen(arg));

没有

arg_list[0] = malloc(strlen(arg) + 1);
strncpy(arg_list[0], arg, strlen(arg) + 1);
arg_list[0][strlen(arg)] = '\0';
arg_list[arg_list_size] = malloc(strlen(arg) + 1);
strncpy(arg_list[arg_list_size], arg, strlen(arg) + 1);
arg_list[arg_list_size][strlen(arg)] = '\0';

技术上有效

arg_list[0] = strdup(arg);
arg_list[arg_list_size] = strdup(arg);

没关系,我猜

这可能会做你认为你想要的,但不是对潜在问题的完全修复,即C