diff options
Diffstat (limited to 'ext/wordexp/wordexp.c')
-rw-r--r-- | ext/wordexp/wordexp.c | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/ext/wordexp/wordexp.c b/ext/wordexp/wordexp.c new file mode 100644 index 0000000..b50ec2d --- /dev/null +++ b/ext/wordexp/wordexp.c @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> + * License: LGPLv2.1 or later (see COPYING for details) + */ +#include <ruby.h> +#include <wordexp.h> +static VALUE cErr; +static VALUE cBadchar; +static VALUE cBadval; +static VALUE cCmdsub; +static VALUE cSyntax; + +struct wordexp_args { + const char *s; + wordexp_t p; + int flags; + int err; +}; + +static void *nogvl_wordexp(void *arg) +{ + struct wordexp_args *a = arg; + + a->err = wordexp(a->s, &a->p, a->flags); + + return NULL; +} + +#if defined(HAVE_RUBY_THREAD_H) && HAVE_RUBY_THREAD_H && \ + defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) +#include <ruby/thread.h> +#define nogvl(fn,arg) rb_thread_call_without_gvl(fn, arg, 0, NULL) +#else +# define COMPAT_FN (VALUE (*)(void *)) +# define nogvl(fn,arg) rb_thread_blocking_region(COMPAT_FN(fn), arg, 0, NULL) +#endif + +/* + * wordexp performs word expansion like a POSIX-shell + * + * call-seq: + * wordexp("hello $(echo world)") -> [ "hello", "world" ] + * wordexp("untrusted string", WRDE_NOCMD) -> [ "untrusted", "string" ] + */ +static VALUE my_wordexp(int argc, VALUE *argv, VALUE self) +{ + VALUE rstr; + VALUE rflags = Qnil; + int retryable = 1; + struct wordexp_args a; + + rb_scan_args(argc, argv, "11", &rstr, &rflags); + + a.s = StringValuePtr(rstr); + a.flags = NIL_P(rflags) ? 0 : NUM2INT(rflags); + +retry: + if (a.flags & WRDE_NOCMD) + a.err = wordexp(a.s, &a.p, a.flags); + else + /* release GVL if command substition is used */ + nogvl(nogvl_wordexp, &a); + + if (a.err == 0) { /* success, map results to a Ruby array */ + VALUE ary = rb_ary_new2(a.p.we_wordc); + char **w = a.p.we_wordv; + size_t i; + + for (i = 0; i < a.p.we_wordc; i++) + rb_ary_store(ary, i, rb_str_new2(w[i])); + + wordfree(&a.p); + + return ary; + } + + switch (a.err) { + case WRDE_BADCHAR: + rb_raise(cBadchar, + "Illegal newline or one of |, &, ;, <, >, (, ), {, }"); + case WRDE_BADVAL: + rb_raise(cBadval, + "undefined shell variable (with WRDE_UNDEF)"); + case WRDE_CMDSUB: + rb_raise(cCmdsub, + "command substition not allowed (WRDE_NOCMD used)"); + case WRDE_NOSPACE: + rb_gc(); + if (!retryable) + rb_memerror(); + retryable = 0; + goto retry; + case WRDE_SYNTAX: + rb_raise(cSyntax, "shell syntax error"); + } + + rb_raise(cErr, "wordexp returned unknown error: %d", a.err); +} + +void Init_wordexp(void) +{ + /* + * Document-module: Wordexp + * + * The Wordexp module only includes the wordexp method and + * WRDE_* constants. Include this module into any class to + * use the wordexp method. + * + * call-seq: + * include Wordexp + */ + VALUE mod = rb_define_module("Wordexp"); + cErr = rb_define_class_under(mod, "WordexpError", rb_eArgError); + + rb_define_method(mod, "wordexp", my_wordexp, -1); + + /* + * Array manipulation is easy and implicit in Ruby, so + * we probably do not need these. + * + * WRDE_APPEND + * WRDE_DOOFFS + * WRDE_REUSE + * + * Please contact us at ruby.wordexp@librelist.org if + * you can make a case for supporting these. + */ + + /* + * Document-constant: WRDE_NOCMD + * Do not do command substition. Command-substition is enabled + * by default, so it is possible to read the standard output of + * arbitrary commands. + */ + rb_define_const(mod, "WRDE_NOCMD", INT2FIX(WRDE_NOCMD)); + + /* + * Document-constant: WRDE_SHOWERR + * Do not redirect stderr to /dev/null when performing + * command substition. Normally, stderr is directed to /dev/null. + */ + rb_define_const(mod, "WRDE_SHOWERR", INT2FIX(WRDE_SHOWERR)); + + /* + * Document-constant: WRDE_UNDEF + * Causes WordexpError::Badval to be raised if an undefined + * environment variable is used. Otherwise undefined variables + * will evaluate to an empty string + */ + rb_define_const(mod, "WRDE_UNDEF", INT2FIX(WRDE_UNDEF)); + + /* + * An illegal character appeared in the argument. + * One of: newline, |, &, ;, <, >, (, ), {, or } + */ + cBadchar = rb_define_class_under(cErr, "Badchar", cErr); + + /* + * An undefined environment variable was referenced and WRDE_UNDEF + * was used + */ + cBadval = rb_define_class_under(cErr, "Badval", cErr); + + /* + * WRDE_NOCMD was specified and command substitution appeared + */ + cCmdsub = rb_define_class_under(cErr, "Cmdsub", cErr); + + /* noexception for WRDE_NOSPACE, rb_memerror instead */ + + /* + * A shell sintax error occurred, such as unbalanced quotes + * or parentheses. + */ + cSyntax = rb_define_class_under(cErr, "Syntax", cErr); +} |