ruby-wordexp.git  about / heads / tags
perform word expansions (interface to the wordexp(3) POSIX function)
blob 1d15b7fe110f24c104b69d0ed8017b9e986a8548 4328 bytes (raw)
$ git show HEAD:ext/wordexp/wordexp.c	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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-public@bogomips.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);
}

git clone https://yhbt.net/ruby-wordexp.git