Skip to content

Commit 54f6198

Browse files
committed
implemented our own nqp::spawn()
1 parent 1161844 commit 54f6198

File tree

2 files changed

+223
-9
lines changed

2 files changed

+223
-9
lines changed

src/vm/parrot/QAST/Operations.nqp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2514,6 +2514,7 @@ QAST::Operations.add_core_pirop_mapping('nfarunalt', 'nqp_nfa_run_alt', '0PsiPPP
25142514
QAST::Operations.add_core_pirop_mapping('exit', 'exit', '0i', :inlinable(1));
25152515
QAST::Operations.add_core_pirop_mapping('sleep', 'sleep', '0n', :inlinable(1));
25162516
QAST::Operations.add_core_pirop_mapping('gethostname', 'nqp_gethostname', 'S');
2517+
QAST::Operations.add_core_pirop_mapping('spawn', 'nqp_spawn', 'IPsP');
25172518
QAST::Operations.add_core_pirop_mapping('shell', 'nqp_shell', 'IssP');
25182519
QAST::Operations.add_core_pirop_mapping('getenvhash', 'nqp_getenvhash', 'P');
25192520

src/vm/parrot/ops/nqp.ops

Lines changed: 222 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -337,14 +337,12 @@ extern char **environ;
337337
#include <windows.h>
338338
#include <process.h>
339339
static char * pack_env_hash(Parrot_Interp interp, PMC* hash_pmc) {
340-
Hash *hash = VTABLE_get_pointer(interp, hash_pmc);
340+
Hash *hash = (Hash *)VTABLE_get_pointer(interp, hash_pmc);
341341
STRING *equal = Parrot_str_new_constant(interp, "=");
342342
STRING *key, *value, *env_var, *env_var_with_null;
343-
INTVAL hash_size = Parrot_hash_size(interp, hash);
344343
STRING *packed = Parrot_str_new_constant(interp, "");
345344
STRING *null = Parrot_str_new(interp, "\0", 1);
346345

347-
348346
/* Parrot_hash_value_to_string is not exported*/
349347
parrot_hash_iterate(hash,
350348
key = (STRING *)_bucket->key;
@@ -355,7 +353,208 @@ static char * pack_env_hash(Parrot_Interp interp, PMC* hash_pmc) {
355353
);
356354
return Parrot_str_to_cstring(interp, packed);
357355
}
356+
#endif
357+
static char **pack_arg_array(Parrot_Interp interp, PMC* array_pmc) {
358+
INTVAL array_size = VTABLE_elements(interp, array_pmc);
359+
char **packed = (char **)mem_sys_allocate((array_size + 1) * sizeof(char *));
360+
INTVAL i = 0;
361+
362+
if (array_size > 0) {
363+
for (i = 0; i < array_size; i++) {
364+
PMC *pmc = VTABLE_get_pmc_keyed_int(interp, array_pmc, i);
365+
STRING *str = (STRING *)VTABLE_get_string(interp, pmc);
366+
#ifdef Win32
367+
(char *)packed[i] = Parrot_str_to_cstring(interp, str);
368+
#else
369+
packed[i] = Parrot_str_to_cstring(interp, str);
370+
#endif
371+
}
372+
}
373+
packed[i] = NULL;
374+
375+
return packed;
376+
}
377+
378+
static int is_space(unsigned char c) {
379+
return c == 0x09 || c == 0x0A || c == 0x0B
380+
|| c == 0x0C || c == 0x0D || c == 0x20
381+
|| c == 0x85;
382+
}
383+
384+
static char *find_next_space(const char *s) {
385+
short in_quotes = 0;
386+
while (*s) {
387+
/* ignore doubled backslashes, or backslash+quote */
388+
if (*s == '\\' && (s[1] == '\\' || s[1] == '"')) {
389+
s += 2;
390+
}
391+
/* keep track of when we're within quotes */
392+
else if (*s == '"') {
393+
s++;
394+
in_quotes = !in_quotes;
395+
}
396+
/* break it up only at spaces that aren't in quotes */
397+
else if (!in_quotes && is_space(*s))
398+
return (char*)s;
399+
else
400+
s++;
401+
}
402+
return (char*)s;
403+
}
404+
405+
/* Autoquoting command-line arguments for nqp::spawn. */
406+
static char *create_command_line(const char *const *args) {
407+
int index, argc;
408+
char *cmd, *ptr;
409+
const char *arg;
410+
size_t len = 0;
411+
short bat_file = 0;
412+
short cmd_shell = 0;
413+
short dumb_shell = 0;
414+
short extra_quotes = 0;
415+
short quote_next = 0;
416+
char *cname = (char*)args[0];
417+
size_t clen = strlen(cname);
418+
419+
/* The NT cmd.exe shell has the following peculiarity that needs to be
420+
* worked around. It strips a leading and trailing dquote when any
421+
* of the following is true:
422+
* 1. the /S switch was used
423+
* 2. there are more than two dquotes
424+
* 3. there is a special character from this set: &<>()@^|
425+
* 4. no whitespace characters within the two dquotes
426+
* 5. string between two dquotes isn't an executable file
427+
* To work around this, we always add a leading and trailing dquote
428+
* to the string, if the first argument is either "cmd.exe" or "cmd",
429+
* and there were at least two or more arguments passed to cmd.exe
430+
* (not including switches).
431+
* XXX the above rules (from "cmd /?") don't seem to be applied
432+
* always, making for the convolutions below :-(
433+
*/
434+
435+
#ifdef WIN32
436+
if (cname) {
437+
if (!clen)
438+
clen = strlen(cname);
439+
440+
if (clen > 4
441+
&& (stricmp(&cname[clen-4], ".bat") == 0
442+
|| (stricmp(&cname[clen-4], ".cmd") == 0))) {
443+
bat_file = 1;
444+
len += 3;
445+
}
446+
else {
447+
char *exe = strrchr(cname, '/');
448+
char *exe2 = strrchr(cname, '\\');
449+
if (exe2 > exe)
450+
exe = exe2;
451+
if (exe)
452+
++exe;
453+
else
454+
exe = cname;
455+
456+
if (stricmp(exe, "cmd.exe") == 0 || stricmp(exe, "cmd") == 0) {
457+
cmd_shell = 1;
458+
len += 3;
459+
}
460+
else if (stricmp(exe, "command.com") == 0
461+
|| stricmp(exe, "command") == 0) {
462+
dumb_shell = 1;
463+
}
464+
}
465+
}
466+
#endif
467+
468+
for (index = 0; (arg = (char*)args[index]) != NULL; ++index) {
469+
size_t curlen = strlen(arg);
470+
if (!(arg[0] == '"' && arg[curlen-1] == '"'))
471+
len += 2; /* assume quoting needed (worst case) */
472+
len += curlen + 1;
473+
}
474+
475+
argc = index;
476+
cmd = (char *)mem_sys_allocate(len * sizeof(char));
477+
ptr = cmd;
478+
479+
if (bat_file) {
480+
*ptr++ = '"';
481+
extra_quotes = 1;
482+
}
483+
484+
for (index = 0; (arg = (char*)args[index]) != NULL; ++index) {
485+
short do_quote = 0;
486+
size_t curlen = strlen(arg);
358487

488+
/* we want to protect empty arguments and ones with spaces with
489+
* dquotes, but only if they aren't already there */
490+
if (!dumb_shell) {
491+
if (!curlen) {
492+
do_quote = 1;
493+
}
494+
else if (quote_next) {
495+
/* see if it really is multiple arguments pretending to
496+
* be one and force a set of quotes around it */
497+
if (*find_next_space(arg))
498+
do_quote = 1;
499+
}
500+
else if (!(arg[0] == '"' && curlen > 1 && arg[curlen-1] == '"')) {
501+
size_t i = 0;
502+
while (i < curlen) {
503+
/* is space */
504+
if (is_space(arg[i])) {
505+
do_quote = 1;
506+
}
507+
else if (arg[i] == '"') {
508+
do_quote = 0;
509+
break;
510+
}
511+
i++;
512+
}
513+
}
514+
}
515+
516+
if (do_quote)
517+
*ptr++ = '"';
518+
519+
strcpy(ptr, arg);
520+
ptr += curlen;
521+
522+
if (do_quote)
523+
*ptr++ = '"';
524+
525+
if (args[index+1])
526+
*ptr++ = ' ';
527+
528+
#ifdef WIN32
529+
if (!extra_quotes
530+
&& cmd_shell
531+
&& curlen >= 2
532+
&& *arg == '/' /* see if arg is "/c", "/x/c", "/x/d/c" etc. */
533+
&& stricmp(arg+curlen-2, "/c") == 0) {
534+
/* is there a next argument? */
535+
if (args[index+1]) {
536+
/* are there two or more next arguments? */
537+
if (args[index+2]) {
538+
*ptr++ = '"';
539+
extra_quotes = 1;
540+
}
541+
else {
542+
/* single argument, force quoting if it has spaces */
543+
quote_next = 1;
544+
}
545+
}
546+
}
547+
#endif
548+
}
549+
550+
if (extra_quotes)
551+
*ptr++ = '"';
552+
553+
*ptr = '\0';
554+
555+
return cmd;
556+
}
557+
#ifdef WIN32
359558
static INTVAL Run_OS_Command(PARROT_INTERP, STRING *command, PMC *env_hash)
360559
{
361560
DWORD status = 0;
@@ -401,11 +600,11 @@ static INTVAL Run_OS_Command(PARROT_INTERP, STRING *command, PMC *env_hash)
401600
#include <sys/wait.h>
402601

403602
static char ** pack_env_hash(Parrot_Interp interp, PMC* hash_pmc) {
404-
Hash *hash = VTABLE_get_pointer(interp, hash_pmc);
603+
Hash *hash = (Hash *)VTABLE_get_pointer(interp, hash_pmc);
405604
STRING *equal = Parrot_str_new_constant(interp, "=");
406605
STRING *key, *value, *env_var;
407606
INTVAL hash_size = Parrot_hash_size(interp, hash);
408-
char** packed = mem_sys_allocate_zeroed(sizeof(char*) * (hash_size+1));
607+
char** packed = (char **)mem_sys_allocate_zeroed(sizeof(char*) * (hash_size+1));
409608
INTVAL i = 0;
410609

411610

@@ -3539,10 +3738,18 @@ inline op nqp_delete_f(out INT, in STR) :base_core {
35393738
#endif
35403739
}
35413740

3542-
inline op nqp_gethostname(out STR) {
3543-
char hostname[65];
3544-
gethostname(hostname, 65);
3545-
$1 = Parrot_str_new_init(interp, hostname, strlen(hostname), Parrot_utf8_encoding_ptr, 0);
3741+
inline op nqp_spawn(out INT, in PMC, in STR, in PMC) {
3742+
STRING *dir = $3;
3743+
PMC *env = $4;
3744+
char *const *argv = pack_arg_array(interp, $2);
3745+
char *args = create_command_line(argv);
3746+
STRING *command = Parrot_str_new(interp, args, 0);
3747+
STRING * const old_cwd = Parrot_file_getcwd(interp);
3748+
Parrot_str_free_cstring(args);
3749+
3750+
Parrot_file_chdir(interp, dir);
3751+
$1 = Run_OS_Command(interp, command, env);
3752+
Parrot_file_chdir(interp, old_cwd);
35463753
}
35473754

35483755
inline op nqp_shell(out INT, in STR, in STR, in PMC) {
@@ -3576,6 +3783,12 @@ inline op nqp_getenvhash(out PMC) {
35763783
}
35773784
}
35783785

3786+
inline op nqp_gethostname(out STR) {
3787+
char hostname[65];
3788+
gethostname(hostname, 65);
3789+
$1 = Parrot_str_new_init(interp, hostname, strlen(hostname), Parrot_utf8_encoding_ptr, 0);
3790+
}
3791+
35793792
/*
35803793

35813794
=item nqp_encode

0 commit comments

Comments
 (0)