[#15067] rb_eval_string — OJ <oj@...7.com>

OJです。

39 messages 2001/11/08
[#15068] Re: rb_eval_string — nobu.nakada@... 2001/11/08

なかだです。

[#15069] Re: rb_eval_string — OJ <oj@...7.com> 2001/11/08

OJです。

[#15071] Re: rb_eval_string — nobu.nakada@... 2001/11/09

なかだです。

[#15077] Re: rb_eval_string — OJ <oj@...7.com> 2001/11/09

OJです。

[#15078] Re: rb_eval_string — WATANABE Hirofumi <eban@...> 2001/11/09

わたなべです。

[#15083] Re: rb_eval_string — "U.Nakamura" <usa@...> 2001/11/09

こんにちは、なかむら(う)です。

[#15088] Re: rb_eval_string — nobu.nakada@... 2001/11/09

なかだです。

[#15089] Re: rb_eval_string — "U.Nakamura" <usa@...> 2001/11/09

こんにちは、なかむら(う)です。

[#15092] Re: rb_eval_string — nobu.nakada@... 2001/11/09

なかだです。

[#15096] Re: rb_eval_string — "U.Nakamura" <usa@...> 2001/11/09

こんにちは、なかむら(う)です。

[#15109] Re: rb_eval_string — WATANABE Hirofumi <eban@...> 2001/11/12

わたなべです。

[#15112] Re: rb_eval_string — "U.Nakamura" <usa@...> 2001/11/12

こんにちは、なかむら(う)です。

[#15114] Re: rb_eval_string — WATANABE Hirofumi <eban@...> 2001/11/12

わたなべです。

[#15115] Re: rb_eval_string — "U.Nakamura" <usa@...> 2001/11/12

こんにちは、なかむら(う)です。

[#15119] Re: rb_eval_string — WATANABE Hirofumi <eban@...> 2001/11/12

わたなべです。

[#15121] Re: rb_eval_string — "U.Nakamura" <usa@...> 2001/11/12

こんにちは、なかむら(う)です。

[#15124] Re: rb_eval_string — WATANABE Hirofumi <eban@...> 2001/11/12

わたなべです。

[#15126] Re: rb_eval_string — "U.Nakamura" <usa@...> 2001/11/12

こんにちは、なかむら(う)です。

[#15174] strange behavior about PTY.spawn — akira yamada / やまだあきら <akira@...>

18 messages 2001/11/15
[#15176] Re: strange behavior about PTY.spawn — matz@... (Yukihiro Matsumoto) 2001/11/15

まつもと ゆきひろです

[#15251] Re: [ruby-ext:01999] Re: syslog module is becoming ready — "Akinori MUSHA" <knu@...>

 というわけで 1.7 に syslog モジュールを入れました。

43 messages 2001/11/26

[#15270] ruby on NetBSD — "U.Nakamura" <usa@...>

こんにちは、なかむら(う)です。

25 messages 2001/11/28
[#15271] Re: ruby on NetBSD — Takahiro Kambe <taca@...> 2001/11/28

In message <20011128181510.3D11.USA@osb.att.ne.jp>

[#15272] Re: ruby on NetBSD — "U.Nakamura" <usa@...> 2001/11/28

こんにちは、なかむら(う)です。

[#15278] Re: ruby on NetBSD — Takahiro Kambe <taca@...> 2001/11/28

In message <20011128182726.3D14.USA@osb.att.ne.jp>

[#15296] Re: ruby on NetBSD — "U.Nakamura" <usa@...> 2001/11/29

こんにちは、なかむら(う)です。

[#15298] time.rb — Tanaka Akira <akr@...17n.org>

というわけで、timex.rb 改め time.rb が rough に入ったのでご意見募集です。

27 messages 2001/11/29

[ruby-dev:15205] Re: strange behavior about PTY.spawn

From: akira yamada / やまだあきら <akira@...>
Date: 2001-11-17 18:22:43 UTC
List: ruby-dev #15205
Fri, 16 Nov 2001 14:20:26 +0900 頃の
   Mail-Count: 15192
      Subject: [ruby-dev:15192] Re: strange behavior about PTY.spawn   
についてのお話にて Yukihiro Matsumoto さん曰く… 
(Y == matz@ruby-lang.org (Yukihiro Matsumoto) さん)

In article 15192, <1005887777.341310.4251.nullmailer@ev.netlab.jp>
Y> 手元ではこういう風にしてます。確信がないのでコミットできませ
Y> んけど。

いろいろいじっていたらプロセスがPTY.spawn{}の範囲をこえて
生きのこってしまうことがあるようでした. 
この点は仕様としてどうあるべきなのかよくわからないのですが, 
ブロック付きの場合にはそのブロックとともに
どうにかなってほしいなあというのがわたしの考えです. 
あと, どういう形でプロセスが終了したのかを知りたいことも
少なくないと思いましたので例外の中にその情報を入れてはどうか? と思い, 
まつもとさんのパッチを適用した上でさらにいじってみました. 

以下パッチです(まつもとさんのパッチも含んでしまっています):

Index: pty.c
===================================================================
RCS file: /home/akira/cvs/ruby-src/cvs/ruby/ext/pty/pty.c,v
retrieving revision 1.9
diff -u -r1.9 pty.c
--- pty.c	13 Nov 2001 08:14:21 -0000	1.9
+++ pty.c	17 Nov 2001 17:57:41 -0000
@@ -106,65 +106,136 @@
 # endif /* HAVE_SETREUID */
 #endif /* NO_SETEUID */
 
-struct pty_info {
-  int fd;
-  pid_t child_pid;
-};
+static VALUE eChildExited;
 
-static void
-pty_raise(cpid)
-    int cpid;
+static VALUE
+echild_exited_code(self)
+    VALUE self;
 {
-    char buf[1024];
+    return rb_ivar_get(self, rb_intern("@exit_code"));
+}
 
-    snprintf(buf, sizeof(buf),
-	     "eval %%Q{Thread.main.raise 'pty - stopped: %d'}, nil, \"%s\", %d",
-	     cpid, ruby_sourcefile, ruby_sourceline);
-    rb_eval_string(buf);
+static VALUE
+echild_exited_signal(self)
+    VALUE self;
+{
+    return rb_ivar_get(self, rb_intern("@received_signal"));
 }
 
 static VALUE
-pty_syswait(pid)
-    int pid;
+echild_exited_stopped(self)
+    VALUE self;
 {
-    int cpid, status;
+    return rb_ivar_get(self, rb_intern("@stopped"));
+}
 
-    cpid = rb_waitpid(pid, &status, WUNTRACED);
+struct pty_info {
+    int fd;
+    pid_t child_pid;
+    VALUE thread;
+};
 
-    printf("PTY command (%d) finished (%d:%d)\n", pid, cpid, status);
-    if (cpid == 0 || cpid == -1)
-	return Qnil;
+static void
+pty_raise(thread, info, status)
+    VALUE thread;
+    struct pty_info *info;
+    int status;
+{
+    char buf[1024];
+    struct child_info *cinfo;
+    NEWOBJ(e, struct RObject);
+    OBJSETUP(e, eChildExited, T_OBJECT);
+
+    if (WIFEXITED(status)) {
+	rb_ivar_set((VALUE)e, rb_intern("@exit_code"), 
+		    INT2FIX(WEXITSTATUS(status)));
+	snprintf(buf, sizeof(buf), "pty - exited (%d): %d", 
+		 WEXITSTATUS(status), info->child_pid);
+    }
+    else {
+	rb_ivar_set((VALUE)e, rb_intern("@exit_code"), Qnil);
+    }
+
+    if (WIFSIGNALED(status)) {
+	rb_ivar_set((VALUE)e, rb_intern("@received_signal"), 
+		    INT2FIX(WTERMSIG(status)));
+	snprintf(buf, sizeof(buf), "pty - received signal (%d): %d", 
+		 WTERMSIG(status), info->child_pid);
+    }
+    else {
+	rb_ivar_set((VALUE)e, rb_intern("@received_signal"), Qnil);
+    }
 
 #ifdef IF_STOPPED
     if (IF_STOPPED(status)) { /* suspend */
-	pty_raise(cpid);
+	snprintf(buf, sizeof(buf), "pty - stopped: %d", info->child_pid);
+	rb_ivar_set((VALUE)e, rb_intern("@stopped"), Qtrue);
     }
 #else
 #ifdef WIFSTOPPED
     if (WIFSTOPPED(status)) { /* suspend */
-	pty_raise(cpid);
+	snprintf(buf, sizeof(buf), "pty - stopped: %d", info->child_pid);
+	rb_ivar_set((VALUE)e, rb_intern("@stopped"), Qtrue);
     }
 #else
 ---->> Either IF_STOPPED or WIFSTOPPED is needed <<----
 #endif /* WIFSTOPPED */
 #endif /* IF_STOPPED */
+    else {
+	rb_ivar_set((VALUE)e, rb_intern("@stopped"), Qfalse);
+    }
+
+    rb_funcall(thread, rb_intern("raise"), 2, (VALUE)e, rb_str_new2(buf));
+}
+
+static VALUE
+pty_syswait(info)
+    struct pty_info *info;
+{
+    int cpid, status;
+
+    cpid = rb_waitpid(info->child_pid, &status, WUNTRACED);
+    printf("cpid: %d (%d)\n", cpid, status);
     
-    return Qnil;
+    if (cpid == 0 || cpid == -1)
+	return Qnil;
+    
+    pty_raise(info->thread, info, status);
 }
 
 static void getDevice _((int*, int*));
 
+struct exec_info {
+    int argc;
+    VALUE *argv;
+};
+
+static VALUE
+pty_exec(arg)
+    struct exec_info *arg;
+{
+    int argc;
+    VALUE *argv;
+    rb_f_exec(arg->argc, arg->argv);
+}
+
 static void
-establishShell(shellname, info)
-    char *shellname;
+establishShell(argc, argv, info)
+    int argc;
+    VALUE *argv;
     struct pty_info *info;
 {	
     static int		i,j,master,slave,currentPid;
     char		*p,*getenv();
     struct passwd	*pwent;
     RETSIGTYPE		chld_changed();
-    
-    if (shellname[0] == '\0') {
+    VALUE v;
+    struct exec_info	arg;
+    int			status;
+
+    if (argc == 0) {
+	char *shellname;
+
 	if ((p = getenv("SHELL")) != NULL) {
 	    shellname = p;
 	}
@@ -175,17 +246,20 @@
 	    else
 		shellname = "/bin/sh";
 	}
+	v = rb_str_new2(shellname);
+	argc = 1;
+	argv = &v;
     }
     getDevice(&master,&slave);
 
     currentPid = getpid();
-    if((i = vfork()) < 0) {
+    if((i = fork()) < 0) {
 	rb_sys_fail("fork failed");
     }
 
     if(i == 0) {	/* child */
-	int argc;
-	char *argv[1024];
+	/* int argc;
+	   char *argv[1024]; */
 	currentPid = getpid();	
 
 	/*
@@ -232,23 +306,13 @@
 	dup2(slave,1);
 	dup2(slave,2);
 	close(slave);
-
 #if defined(HAVE_SETEUID) || defined(HAVE_SETREUID) || defined(HAVE_SETRESUID)
 	seteuid(getuid());
 #endif
 
-	argc = 0;
-	for (i = 0; shellname[i];) {
-	    while (isspace(shellname[i])) i++;
-	    for (j = i; shellname[j] && !isspace(shellname[j]); j++);
-	    argv[argc] = (char*)xmalloc(j-i+1);
-	    strncpy(argv[argc],&shellname[i],j-i);
-	    argv[argc][j-i] = 0;
-	    i = j;
-	    argc++;
-	}
-	argv[argc] = NULL;
-	execvp(argv[0],argv);
+	arg.argc = argc;
+	arg.argv = argv;
+	rb_protect(pty_exec, (VALUE)&arg, &status);
 	sleep(1);
 	_exit(1);
     }
@@ -359,12 +423,38 @@
     chown(SlaveName, 0, 0);
 }
 
+static VALUE
+pty_kill_child(thinfo)
+    VALUE thinfo;
+{
+    VALUE th = RARRAY(thinfo)->ptr[0];
+    pid_t pid = RARRAY(thinfo)->ptr[1];
+
+    if (rb_funcall(th, rb_intern("alive?"), 0, 0) == Qtrue &&
+	kill(pid, 0) == 0) {
+	rb_thread_schedule();
+	if (kill(pid, SIGTERM) == 0) {
+	    rb_thread_schedule();
+	    if (kill(pid, 0) == 0) {
+		kill(pid, SIGINT);
+		rb_thread_schedule();
+		if (kill(pid, 0) == 0)
+		    kill(pid, SIGKILL);
+	    }
+	}
+    }
+    rb_funcall(th, rb_intern("value"), 0, 0);
+    return Qnil;
+}
+
 /* ruby function: getpty */
 static VALUE
-pty_getpty(self, command)
-    VALUE self, command;
+pty_getpty(argc, argv, self)
+    int argc;
+    VALUE *argv;
+    VALUE self;
 {
-    VALUE res, th;
+    VALUE res, th, thinfo;
     struct pty_info info;
     OpenFile *wfptr,*rfptr;
     VALUE rport = rb_obj_alloc(rb_cFile);
@@ -373,31 +463,33 @@
     MakeOpenFile(rport, rfptr);
     MakeOpenFile(wport, wfptr);
 
-    if (TYPE(command) == T_ARRAY)
-	command = rb_ary_join(command,rb_str_new2(" "));
-    Check_SafeStr(command);
-
-    establishShell(RSTRING(command)->ptr,&info);
+    establishShell(argc, argv, &info);
 
     rfptr->mode = rb_io_mode_flags("r");
     rfptr->f = fdopen(info.fd, "r");
-    rfptr->path = strdup(RSTRING(command)->ptr);
+    rfptr->path = 0; /*strdup(RSTRING(command)->ptr); */
 
     wfptr->mode = rb_io_mode_flags("w");
     wfptr->f = fdopen(dup(info.fd), "w");
-    wfptr->path = strdup(RSTRING(command)->ptr);
+    wfptr->path = 0; /* strdup(RSTRING(command)->ptr); */
 
     res = rb_ary_new2(3);
     rb_ary_store(res,0,(VALUE)rport);
     rb_ary_store(res,1,(VALUE)wport);
     rb_ary_store(res,2,INT2FIX(info.child_pid));
 
-    printf("start watching PTY command (%d)\n", info.child_pid);
-    th = rb_thread_create(pty_syswait, (void*)info.child_pid);
+    info.thread = rb_thread_current();
+    printf("thread %p\n", info.thread);
+    th = rb_thread_create(pty_syswait, (void*)&info);
+
+    thinfo = rb_ary_new2(2);
+    rb_ary_store(thinfo,0,(VALUE)th);
+    rb_ary_store(thinfo,1,(VALUE)info.child_pid);
+
     if (rb_block_given_p()) {
-	res = rb_yield((VALUE)res);
-	rb_funcall(th, rb_intern("kill"), 0, 0);
-	return res;
+	rb_ensure(rb_yield, (VALUE)res, 
+	    pty_kill_child, (VALUE)thinfo);
+	return Qnil;
     }
     else {
 	return res;
@@ -429,8 +521,13 @@
 Init_pty()
 {
     cPTY = rb_define_module("PTY");
-    rb_define_module_function(cPTY,"getpty",pty_getpty,1);
-    rb_define_module_function(cPTY,"spawn",pty_getpty,1);
+    rb_define_module_function(cPTY,"getpty",pty_getpty,-1);
+    rb_define_module_function(cPTY,"spawn",pty_getpty,-1);
     rb_define_module_function(cPTY,"protect_signal",pty_protect,0);
     rb_define_module_function(cPTY,"reset_signal",pty_reset_signal,0);
+
+    eChildExited = rb_define_class_under(cPTY,"ChildExited",rb_eRuntimeError);
+    rb_define_method(eChildExited,"exit_code",echild_exited_code,0);
+    rb_define_method(eChildExited,"received_signal",echild_exited_signal,0);
+    rb_define_method(eChildExited,"stopped?",echild_exited_stopped,0);
 }


ホントに簡単なものですがテストも作ってみました. 

require 'runit/testcase'
require 'runit/cui/testrunner'
require 'pty'

class TestPTY < RUNIT::TestCase
  def setup
    @s = Signal::list
  end

  def test_STOP
    pid = nil
    e = assert_exception(PTY::ChildExited) {
      PTY.spawn('/bin/sh') {|rd,wr,pid|
	assert_equal(1, Process.kill(@s['STOP'], pid))
	sleep 1
      }
    }
    assert(pid != nil)
    assert_equal(1, Process.kill(0, pid))
    assert_equal(nil, e.exit_code)
    assert_equal(nil, e.received_signal)
    assert_equal(true, e.stopped?)
  end
  def test_KILL
    pid = nil
    e = assert_exception(PTY::ChildExited) {
      PTY.spawn('/bin/sh') {|rd,wr,pid|
	assert_equal(1, Process.kill(@s['KILL'], pid))
	sleep 1
      }
    }
    assert(pid != nil)
    assert_exception(Errno::ESRCH) {Process.kill(0, pid)}
    assert_equal(nil, e.exit_code)
    assert_equal(@s['KILL'], e.received_signal)
    assert_equal(false, e.stopped?)
  end
  def test_exit1
    pid = nil
    e = assert_exception(PTY::ChildExited) {
      PTY.spawn('/bin/sh'){|rd,wr,pid|
	wr.puts 'echo 123'
	wr.puts 'exit'
	loop {rd.sysread(1024)} rescue IOError
      }
    }
    assert_exception(Errno::ESRCH) {Process.kill(0, pid)}
    assert_equal(0, e.exit_code)
    assert_equal(nil, e.received_signal)
    assert_equal(false, e.stopped?)
  end
  def test_exit2
    pid = nil
    e = assert_exception(PTY::ChildExited) {
      PTY.spawn('sleep 3') {|rd,wr,pid|
	assert_equal(1, Process.kill(0, pid))
	sleep
      }
    }
    assert_exception(Errno::ESRCH) {Process.kill(0, pid)}
    assert_equal(0, e.exit_code)
    assert_equal(nil, e.received_signal)
    assert_equal(false, e.stopped?)
  end
  def test_notexist
    pid = nil
    e = assert_exception(PTY::ChildExited) {
      PTY.spawn('/not exist') {|rd,wr,pid|
	sleep 3
	assert_fail
      }
    }
    assert_equal(1, e.exit_code)
    assert_equal(nil, e.received_signal)
    assert_equal(false, e.stopped?)
  end

  def test_thSTOP
    th = Thread.new {test_STOP}
    loop {sleep 0.5; break unless th.alive?}
  end
  def test_thKILL
    th = Thread.new {test_KILL}
    loop {sleep 0.5; break unless th.alive?}
  end
  def test_thexit1
    th = Thread.new {test_exit1}
    loop {sleep 0.5; break unless th.alive?}
  end
  def test_thexit2
    th = Thread.new {test_exit2}
    loop {sleep 0.5; break unless th.alive?}
  end
  def test_thnotexist
    th = Thread.new {test_notexist}
    loop {sleep 0.5; break unless th.alive?}
  end
end

if $0 == __FILE__
  if ARGV.size == 0
    suite = TestPTY.suite
  else
    suite = RUNIT::TestSuite.new
    ARGV.each do |testmethod|
      suite.add(TestPTY.new(testmethod))
    end
  end
  RUNIT::CUI::TestRunner.run(suite)
end

微妙な点もまだ残っていそうな気がしますが, 
どんなものでしょうか?

-- 
 やまだ あきら <URL:http://arika.org/>
 (akira@arika.org, akira@ruby-lang.org or akira@linux.or.jp)

In This Thread