# New Ticket Created by  Kay-Uwe Huell 
# Please include the string:  [perl #41353]
# in the subject line of all future correspondence about this issue. 
# <URL: http://rt.perl.org/rt3/Ticket/Display.html?id=41353 >


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi parrot team,

I have started to call external programs in Linux environment and had to
recognize, that
   open "/some/command some argument", "-|"
is VERY limited. (Could not use quotes to use space in arguments, could
only use max. 10 arguments, etc.)

I have extended the function to be still basic (not to use perl's magic
shell usage when it encounters a special character), but that you can
use quotes to have whitespace in arguments and that you can have as many
arguments you like.

Patch attached.

Regards,

Kiwi

 src/io/io_unix.c |  147
++++++++++++++++++++++++++++++++++++++++++++++---------
 t/pmc/io.t       |   97 +++++++++++++++++++++++++++++++++++-
 2 files changed, 220 insertions(+), 24 deletions(-)
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.3 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFFukPNHSiAp6bcIcgRAiHnAKCoSTz9JChlnwzGIcHckHWwD3b+MQCePyF7
GuV6KPdUgcIJ9NVwkRsQeWg=
=Kjo7
-----END PGP SIGNATURE-----
Index: src/io/io_unix.c
===================================================================
--- src/io/io_unix.c	(Revision 16797)
+++ src/io/io_unix.c	(Arbeitskopie)
@@ -21,8 +21,7 @@
 
 =head2 Functions
 
-=over 4
-
+=over 4 
 =cut
 
 */
@@ -1009,16 +1008,25 @@
 =item C<static ParrotIO *
 PIO_unix_pipe(Interp *interp, ParrotIOLayer *l, STRING *cmd, int flags)>
 
-Very limited C<exec> for now.
+This function executes C<cmd> and reads from or writes to pipe. 
 
+If you read from pipe you usually get only stdout from C<cmd>. If you append
+a "2>&1" to your command stderr will be redirected to stdout, which means you
+get stderr as well. You may use single quote C<'> or double quote C<"> to use
+whitespace in your command's arguments. If you do not match a quote you started
+an exception will be thrown.
+
+Right now there is no shell magic as in perl (recognize special chars and 
+execute sh -c 'cmd'). You may use C</bin/sh -c 'cmd'> yourself. You always have
+to use complete path of command you want to execute, because there is no path
+searching!
+
 XXX: Where does this fit, should it belong in the ParrotIOLayerAPI?
 
 =cut
 
 */
 
-
-
 static ParrotIO *
 PIO_unix_pipe(Interp *interp, ParrotIOLayer *l, const char *cmd, int flags)
 {
@@ -1059,9 +1067,44 @@
 
     /* Child - exec process */
     if (pid == 0) {
-        char *argv[10], *p, *c;
-        int n;
+        char **argv, *p, *_cmd;
+        int n, i, doshell = 0, redirect_stderr = 0, argstep = 10 ;
+        
 
+        /* usually STDOUT will be redirected to pipe.
+
+           If you use magic '2>&1' at the end of the command, STDERR will be
+           redirected as well.
+         */
+
+        _cmd = strdup(cmd);
+
+        /* skip whitespace */
+        for ( ; *_cmd && isspace(*_cmd) ; _cmd++);
+
+        /* Check for magic '2>&1' at the end of command */
+        for (p = _cmd ; ; )
+        {
+            p = strstr(p, "2>&1"); 
+            if (p && isspace(p[-1]) && (!p[4] || isspace(p[4]))) {
+                char *q = p;
+                for (p += 4; *p && isspace(*p); p++);
+
+                /* if there is something after 2>&1, 2>&1 belongs to the 
+                   command, and we have to check again */
+                if (*p) 
+                    continue;
+
+                /* 2>&1 and the rest of string has to be cut off */
+                *q = '\0';
+                redirect_stderr = 1;
+                break;
+            }
+            /* there is no "2>&1" in p */
+            break;
+        }
+
+
         if (flags & PIO_F_WRITE) {
             /* the other end is writing - we read from the pipe */
             close(STDIN_FILENO);
@@ -1074,27 +1117,85 @@
             /* XXX redirect stdout, stderr to pipe */
             close(STDIN_FILENO);
             close(STDOUT_FILENO);
-            close(STDERR_FILENO);
-            if ( dup(fds[0]) != STDIN_FILENO || dup(fds[1]) != STDOUT_FILENO
-                    || dup(fds[1]) != STDERR_FILENO )
-            {
+            if ( dup(fds[0]) != STDIN_FILENO || dup(fds[1]) != STDOUT_FILENO )
                 exit(0);
+             
+            if (redirect_stderr) {
+                close(STDERR_FILENO);
+                if (dup(fds[1]) != STDERR_FILENO) 
+                    exit(0);
             }
         }
-        /*
-         * XXX ugly hack to be able to pass some arguments
-         *     split cmd at blanks
-         */
-        c = strdup(cmd);
-        for (n = 0, p = strtok(c, " "); n < 9 && p; p = strtok(NULL, " ")) {
-            if (n == 0)
-                cmd = p;
-            argv[n++] = p;
+
+        argv = mem_sys_allocate(argstep * sizeof(char *));
+
+        /* now prepare argv array. Split on ' '. Check for "..." and '...' in 
+           string. Watch out for excaped " and ' */
+
+/* This local macro checks if count of '\' is odd or equal. 
+   odd => escaped => continue */
+#define CONTINUE_IF_ESCAPED if (p[-1] == '\\') { \
+                               char *r = p-1; \
+                               for(; *r && *r == '\\'; r--); \
+                               if ((p-r-1) % 2 == 1) continue; }
+
+        argv[0] = _cmd;
+        for (n = 1, p = _cmd; *p; p++) {
+            if (isspace(*p)) {
+                /* skip more space */
+                for (; isspace(*p); p++ ) 
+                    *p = '\0';
+                argv[n++] = p;
+
+                /* resize array by 10 */
+                if ((n % 10) == 0) {
+                   /* do not use mem_sys_realloc() here. It currupts the argv 
+                      array! */
+                   char **new_argv ;
+                   new_argv = mem_sys_allocate((n + 10) * sizeof(char *));
+                   memcpy(new_argv, argv, n * sizeof(char *));
+                   argv = new_argv;
+                 }
+            }
+
+            if (*p == '"' || *p == '\'') {
+                char quote_char = *p ;
+
+                CONTINUE_IF_ESCAPED ;
+ 
+                for (p++ ; ; ) {
+                    p = strchr(p, quote_char) ;
+                    if (!p) {
+                        /* exception no matching quote_char */
+                        real_exception(interp, NULL, E_ValueError, 
+                                       "no matching '%c'", quote_char);
+                    }
+                    CONTINUE_IF_ESCAPED ;
+                    break;
+                }
+            }
         }
-        argv[n] = NULL;
-        execv(cmd, argv);       /* XXX use execvp ? */
+
+        argv[n] = NULL ;
+
+        for (i = 0 ; argv[i] != NULL ; i++) {
+           /* remove quoting chars */
+           if (argv[i][0] == '"' || argv[i][0] == '\'')
+           {
+             char *p = strrchr(argv[i], argv[i][0]);
+             (argv[i])++ ; *p = '\0' ;
+           }
+        }
+        execv(argv[0], argv);
+
         /* Will never reach this unless exec fails. */
-        perror("execvp");
+        perror("execv");
+        fprintf(stderr, "cmd: %s\n", argv[0]);
+        for (i = 0 ; i < n ; i++)
+        {
+           fprintf(stderr, "%d: %s\n", i, argv[i]);
+        }
+
         exit(1);
     }
 
Index: t/pmc/io.t
===================================================================
--- t/pmc/io.t	(Revision 16797)
+++ t/pmc/io.t	(Arbeitskopie)
@@ -6,7 +6,7 @@
 use warnings;
 use lib qw( . lib ../lib ../../lib );
 use Test::More;
-use Parrot::Test tests => 44;
+use Parrot::Test tests => 47;
 
 =head1 NAME
 
@@ -902,6 +902,101 @@
 OUTPUT
 }
 
+SKIP: {
+    skip 'will not work on windows', 1 if $^O eq 'MSWin32';
+    open IN, "/bin/sh -c \"FOO=. ls\" |" ;
+    my $text ;
+    $text .= $_ while <IN> ;
+    close IN ;
+    #
+    # I need to have separeted stdout and stderr for testing unix_pipe
+    #
+    open CODE_FILE, ">/tmp/$$.pir" ;
+    print CODE_FILE <<'CODE' ;
+.sub execute 
+    .param string cmd
+
+    .local pmc pipe
+    pipe = open cmd, "-|"
+    unless_null pipe, READ_PIPE
+    print "error\n"
+    exit 1
+
+  READ_PIPE:
+    # unbuffered read
+    $S0 = pop pipe
+
+  READ_NEXT:
+    $S0 = read pipe, 10
+    $S1 .= $S0
+    if pipe goto READ_NEXT
+  
+  DONE:
+    close pipe
+
+    .return($S1)
+.end
+
+.sub main :main
+    $S0 = execute("/bin/sh -c \"echo foo 1>&2 ; echo bar\"")
+    print $S0
+    $S0 = execute("/bin/sh -c 'echo foo 1>&2 ; echo \"bar\" 2>&1'")
+    print $S0
+    $S0 = execute("/bin/sh -c \"echo foo 1>&2 ; echo bar\" 2>&1")
+    print $S0
+    exit 0
+.end
+CODE
+   close CODE_FILE ;
+
+   my $stdout = `./parrot /tmp/$$.pir 2>/dev/null` ;
+   my $stderr = `./parrot /tmp/$$.pir 1>/dev/null 2>/tmp/$$.err ; cat /tmp/$$.err` ;
+   unlink "/tmp/$$.pir" ;
+   unlink "/tmp/$$.err" ;
+
+   ok($stdout eq "bar\nbar\nfoo\nbar\n", "pipe's output to stdout");
+   ok($stderr eq "foo\nfoo\n", "pipe's output to stderr");
+}    
+
+SKIP: {
+    skip 'will not work on windows', 1 if $^O eq 'MSWin32';
+    open IN, "echo 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 |" ;
+    my $text ;
+    $text .= $_ while <IN> ;
+    close IN ;
+    pir_output_is(<<'CODE', <<"OUTPUT", "many arguments");
+.sub main :main
+    .local pmc pipe
+    pipe = open "/bin/echo 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6", "-|"
+    unless_null pipe, READ_PIPE
+    print "error\n"
+    exit 1
+
+  READ_PIPE:
+    $S0 = pop pipe
+    print "popped "
+    print $S0
+    print "\n"
+ 
+  READ_NEXT:
+    $S0 = read pipe, 10
+    $S1 .= $S0
+    if pipe goto READ_NEXT
+  
+  DONE:
+    close pipe
+
+    print $S1
+    print "\n"
+    exit 0
+.end
+CODE
+popped buf
+$text
+OUTPUT
+}    
+
+
 # Local Variables:
 #   mode: cperl
 #   cperl-indent-level: 4

Attachment: unix_pipe.patch.sig
Description: Binary data

Reply via email to