我最近制作了以下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在脚本成功的地方失败了?谢谢!
答案 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