xref: /php-src/sapi/phpdbg/phpdbg_cmd.c (revision cc8c24ab)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Felipe Pena <felipe@php.net>                                |
14    | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
15    | Authors: Bob Weinand <bwoebi@php.net>                                |
16    +----------------------------------------------------------------------+
17 */
18 
19 #include "phpdbg.h"
20 #include "phpdbg_cmd.h"
21 #include "phpdbg_utils.h"
22 #include "phpdbg_set.h"
23 #include "phpdbg_prompt.h"
24 #include "phpdbg_io.h"
25 
ZEND_EXTERN_MODULE_GLOBALS(phpdbg)26 ZEND_EXTERN_MODULE_GLOBALS(phpdbg)
27 
28 static inline const char *phpdbg_command_name(const phpdbg_command_t *command, char *buffer) {
29 	size_t pos = 0;
30 
31 	if (command->parent) {
32 		memcpy(&buffer[pos], command->parent->name, command->parent->name_len);
33 		pos += command->parent->name_len;
34 		memcpy(&buffer[pos], " ", sizeof(" ")-1);
35 		pos += (sizeof(" ")-1);
36 	}
37 
38 	memcpy(&buffer[pos], command->name, command->name_len);
39 	pos += command->name_len;
40 	buffer[pos] = 0;
41 
42 	return buffer;
43 }
44 
phpdbg_get_param_type(const phpdbg_param_t * param)45 PHPDBG_API const char *phpdbg_get_param_type(const phpdbg_param_t *param) /* {{{ */
46 {
47 	switch (param->type) {
48 		case STACK_PARAM:
49 			return "stack";
50 		case EMPTY_PARAM:
51 			return "empty";
52 		case ADDR_PARAM:
53 			return "address";
54 		case NUMERIC_PARAM:
55 			return "numeric";
56 		case METHOD_PARAM:
57 			return "method";
58 		case NUMERIC_FUNCTION_PARAM:
59 			return "function opline";
60 		case NUMERIC_METHOD_PARAM:
61 			return "method opline";
62 		case FILE_PARAM:
63 			return "file or file opline";
64 		case STR_PARAM:
65 			return "string";
66 		default: /* this is bad */
67 			return "unknown";
68 	}
69 }
70 
phpdbg_clear_param(phpdbg_param_t * param)71 PHPDBG_API void phpdbg_clear_param(phpdbg_param_t *param) /* {{{ */
72 {
73 	if (param) {
74 		switch (param->type) {
75 			case FILE_PARAM:
76 				efree(param->file.name);
77 				break;
78 			case METHOD_PARAM:
79 				efree(param->method.class);
80 				efree(param->method.name);
81 				break;
82 			case STR_PARAM:
83 				efree(param->str);
84 				break;
85 			default:
86 				break;
87 		}
88 	}
89 
90 } /* }}} */
91 
phpdbg_param_tostring(const phpdbg_param_t * param,char ** pointer)92 PHPDBG_API char* phpdbg_param_tostring(const phpdbg_param_t *param, char **pointer) /* {{{ */
93 {
94 	switch (param->type) {
95 		case STR_PARAM:
96 			ZEND_IGNORE_VALUE(asprintf(pointer, "%s", param->str));
97 		break;
98 
99 		case ADDR_PARAM:
100 			ZEND_IGNORE_VALUE(asprintf(pointer, ZEND_ULONG_FMT, param->addr));
101 		break;
102 
103 		case NUMERIC_PARAM:
104 			ZEND_IGNORE_VALUE(asprintf(pointer, ZEND_LONG_FMT, param->num));
105 		break;
106 
107 		case METHOD_PARAM:
108 			ZEND_IGNORE_VALUE(asprintf(pointer, "%s::%s", param->method.class, param->method.name));
109 		break;
110 
111 		case FILE_PARAM:
112 			if (param->num) {
113 				ZEND_IGNORE_VALUE(asprintf(pointer, "%s:"ZEND_ULONG_FMT"#"ZEND_ULONG_FMT, param->file.name, param->file.line, param->num));
114 			} else {
115 				ZEND_IGNORE_VALUE(asprintf(pointer, "%s:"ZEND_ULONG_FMT, param->file.name, param->file.line));
116 			}
117 		break;
118 
119 		case NUMERIC_FUNCTION_PARAM:
120 			ZEND_IGNORE_VALUE(asprintf(pointer, "%s#"ZEND_ULONG_FMT, param->str, param->num));
121 		break;
122 
123 		case NUMERIC_METHOD_PARAM:
124 			ZEND_IGNORE_VALUE(asprintf(pointer, "%s::%s#"ZEND_ULONG_FMT, param->method.class, param->method.name, param->num));
125 		break;
126 
127 		default:
128 			*pointer = strdup("unknown");
129 	}
130 
131 	return *pointer;
132 } /* }}} */
133 
phpdbg_copy_param(const phpdbg_param_t * src,phpdbg_param_t * dest)134 PHPDBG_API void phpdbg_copy_param(const phpdbg_param_t* src, phpdbg_param_t* dest) /* {{{ */
135 {
136 	switch ((dest->type = src->type)) {
137 		case STACK_PARAM:
138 			/* nope */
139 		break;
140 
141 		case STR_PARAM:
142 			dest->str = estrndup(src->str, src->len);
143 			dest->len = src->len;
144 		break;
145 
146 		case OP_PARAM:
147 			dest->str = estrndup(src->str, src->len);
148 			dest->len = src->len;
149 		break;
150 
151 		case ADDR_PARAM:
152 			dest->addr = src->addr;
153 		break;
154 
155 		case NUMERIC_PARAM:
156 			dest->num = src->num;
157 		break;
158 
159 		case METHOD_PARAM:
160 			dest->method.class = estrdup(src->method.class);
161 			dest->method.name = estrdup(src->method.name);
162 		break;
163 
164 		case NUMERIC_FILE_PARAM:
165 		case FILE_PARAM:
166 			dest->file.name = estrdup(src->file.name);
167 			dest->file.line = src->file.line;
168 			if (src->num)
169 				dest->num   = src->num;
170 		break;
171 
172 		case NUMERIC_FUNCTION_PARAM:
173 			dest->str = estrndup(src->str, src->len);
174 			dest->num = src->num;
175 			dest->len = src->len;
176 		break;
177 
178 		case NUMERIC_METHOD_PARAM:
179 			dest->method.class = estrdup(src->method.class);
180 			dest->method.name = estrdup(src->method.name);
181 			dest->num = src->num;
182 		break;
183 
184 		case EMPTY_PARAM: { /* do nothing */ } break;
185 
186 		default: {
187 			/* not yet */
188 		}
189 	}
190 } /* }}} */
191 
phpdbg_hash_param(const phpdbg_param_t * param)192 PHPDBG_API zend_ulong phpdbg_hash_param(const phpdbg_param_t *param) /* {{{ */
193 {
194 	zend_ulong hash = param->type;
195 
196 	switch (param->type) {
197 		case STACK_PARAM:
198 			/* nope */
199 		break;
200 
201 		case STR_PARAM:
202 			hash += zend_hash_func(param->str, param->len);
203 		break;
204 
205 		case METHOD_PARAM:
206 			hash += zend_hash_func(param->method.class, strlen(param->method.class));
207 			hash += zend_hash_func(param->method.name, strlen(param->method.name));
208 		break;
209 
210 		case FILE_PARAM:
211 			hash += zend_hash_func(param->file.name, strlen(param->file.name));
212 			hash += param->file.line;
213 			if (param->num)
214 				hash += param->num;
215 		break;
216 
217 		case ADDR_PARAM:
218 			hash += param->addr;
219 		break;
220 
221 		case NUMERIC_PARAM:
222 			hash += param->num;
223 		break;
224 
225 		case NUMERIC_FUNCTION_PARAM:
226 			hash += zend_hash_func(param->str, param->len);
227 			hash += param->num;
228 		break;
229 
230 		case NUMERIC_METHOD_PARAM:
231 			hash += zend_hash_func(param->method.class, strlen(param->method.class));
232 			hash += zend_hash_func(param->method.name, strlen(param->method.name));
233 			if (param->num)
234 				hash+= param->num;
235 		break;
236 
237 		case EMPTY_PARAM: { /* do nothing */ } break;
238 
239 		default: {
240 			/* not yet */
241 		}
242 	}
243 
244 	return hash;
245 } /* }}} */
246 
phpdbg_match_param(const phpdbg_param_t * l,const phpdbg_param_t * r)247 PHPDBG_API bool phpdbg_match_param(const phpdbg_param_t *l, const phpdbg_param_t *r) /* {{{ */
248 {
249 	if (l && r) {
250 		if (l->type == r->type) {
251 			switch (l->type) {
252 				case STACK_PARAM:
253 					/* nope, or yep */
254 					return 1;
255 				break;
256 
257 				case NUMERIC_FUNCTION_PARAM:
258 					if (l->num != r->num) {
259 						break;
260 					}
261 				ZEND_FALLTHROUGH;
262 
263 				case STR_PARAM:
264 					return (l->len == r->len) &&
265 							(memcmp(l->str, r->str, l->len) == SUCCESS);
266 
267 				case NUMERIC_PARAM:
268 					return (l->num == r->num);
269 
270 				case ADDR_PARAM:
271 					return (l->addr == r->addr);
272 
273 				case FILE_PARAM: {
274 					if (l->file.line == r->file.line) {
275 						size_t lengths[2] = {
276 							strlen(l->file.name), strlen(r->file.name)};
277 
278 						if (lengths[0] == lengths[1]) {
279 							if ((!l->num && !r->num) || (l->num == r->num)) {
280 								return (memcmp(
281 									l->file.name, r->file.name, lengths[0]) == SUCCESS);
282 							}
283 						}
284 					}
285 				} break;
286 
287 				case NUMERIC_METHOD_PARAM:
288 					if (l->num != r->num) {
289 						break;
290 					}
291 				ZEND_FALLTHROUGH;
292 
293 				case METHOD_PARAM: {
294 					size_t lengths[2] = {
295 						strlen(l->method.class), strlen(r->method.class)};
296 					if (lengths[0] == lengths[1]) {
297 						if (memcmp(l->method.class, r->method.class, lengths[0]) == SUCCESS) {
298 							lengths[0] = strlen(l->method.name);
299 							lengths[1] = strlen(r->method.name);
300 
301 							if (lengths[0] == lengths[1]) {
302 								return (memcmp(
303 									l->method.name, r->method.name, lengths[0]) == SUCCESS);
304 							}
305 						}
306 					}
307 				} break;
308 
309 				case EMPTY_PARAM:
310 					return 1;
311 
312 				default: {
313 					/* not yet */
314 				}
315 			}
316 		}
317 	}
318 	return 0;
319 } /* }}} */
320 
321 /* {{{ */
phpdbg_param_debug(const phpdbg_param_t * param,const char * msg)322 PHPDBG_API void phpdbg_param_debug(const phpdbg_param_t *param, const char *msg) {
323 	if (param && param->type) {
324 		switch (param->type) {
325 			case STR_PARAM:
326 				fprintf(stderr, "%s STR_PARAM(%s=%zu)\n", msg, param->str, param->len);
327 			break;
328 
329 			case ADDR_PARAM:
330 				fprintf(stderr, "%s ADDR_PARAM(" ZEND_ULONG_FMT ")\n", msg, param->addr);
331 			break;
332 
333 			case NUMERIC_FILE_PARAM:
334 				fprintf(stderr, "%s NUMERIC_FILE_PARAM(%s:#"ZEND_ULONG_FMT")\n", msg, param->file.name, param->file.line);
335 			break;
336 
337 			case FILE_PARAM:
338 				fprintf(stderr, "%s FILE_PARAM(%s:"ZEND_ULONG_FMT")\n", msg, param->file.name, param->file.line);
339 			break;
340 
341 			case METHOD_PARAM:
342 				fprintf(stderr, "%s METHOD_PARAM(%s::%s)\n", msg, param->method.class, param->method.name);
343 			break;
344 
345 			case NUMERIC_METHOD_PARAM:
346 				fprintf(stderr, "%s NUMERIC_METHOD_PARAM(%s::%s)\n", msg, param->method.class, param->method.name);
347 			break;
348 
349 			case NUMERIC_FUNCTION_PARAM:
350 				fprintf(stderr, "%s NUMERIC_FUNCTION_PARAM(%s::"ZEND_LONG_FMT")\n", msg, param->str, param->num);
351 			break;
352 
353 			case NUMERIC_PARAM:
354 				fprintf(stderr, "%s NUMERIC_PARAM("ZEND_LONG_FMT")\n", msg, param->num);
355 			break;
356 
357 			case COND_PARAM:
358 				fprintf(stderr, "%s COND_PARAM(%s=%zu)\n", msg, param->str, param->len);
359 			break;
360 
361 			case OP_PARAM:
362 				fprintf(stderr, "%s OP_PARAM(%s=%zu)\n", msg, param->str, param->len);
363 			break;
364 
365 			default: {
366 				/* not yet */
367 			}
368 		}
369 	}
370 } /* }}} */
371 
372 /* {{{ */
phpdbg_stack_free(phpdbg_param_t * stack)373 PHPDBG_API void phpdbg_stack_free(phpdbg_param_t *stack) {
374 	ZEND_ASSERT(stack != NULL);
375 
376 	if (stack->next) {
377 		phpdbg_param_t *remove = stack->next;
378 
379 		while (remove) {
380 			phpdbg_param_t *next = NULL;
381 
382 			if (remove->next)
383 				next = remove->next;
384 
385 			switch (remove->type) {
386 				case NUMERIC_METHOD_PARAM:
387 				case METHOD_PARAM:
388 					if (remove->method.class) {
389 						efree(remove->method.class);
390 					}
391 					if (remove->method.name) {
392 						efree(remove->method.name);
393 					}
394 				break;
395 
396 				case NUMERIC_FUNCTION_PARAM:
397 				case STR_PARAM:
398 				case OP_PARAM:
399 				case EVAL_PARAM:
400 				case SHELL_PARAM:
401 				case COND_PARAM:
402 				case RUN_PARAM:
403 					if (remove->str) {
404 						efree(remove->str);
405 					}
406 				break;
407 
408 				case NUMERIC_FILE_PARAM:
409 				case FILE_PARAM:
410 					if (remove->file.name) {
411 						efree(remove->file.name);
412 					}
413 				break;
414 
415 				default: {
416 					/* nothing */
417 				}
418 			}
419 
420 			free(remove);
421 			remove = NULL;
422 
423 			if (next)
424 				remove = next;
425 			else break;
426 		}
427 
428 		stack->next = NULL;
429 	}
430 } /* }}} */
431 
432 /* {{{ */
phpdbg_stack_push(phpdbg_param_t * stack,phpdbg_param_t * param)433 PHPDBG_API void phpdbg_stack_push(phpdbg_param_t *stack, phpdbg_param_t *param) {
434 	phpdbg_param_t *next = calloc(1, sizeof(phpdbg_param_t));
435 
436 	if (!next) {
437 		return;
438 	}
439 
440 	*(next) = *(param);
441 
442 	next->next = NULL;
443 
444 	if (stack->top == NULL) {
445 		stack->top = next;
446 		next->top = NULL;
447 		stack->next = next;
448 	} else {
449 		stack->top->next = next;
450 		next->top = stack->top;
451 		stack->top = next;
452 	}
453 
454 	stack->len++;
455 } /* }}} */
456 
457 /* {{{ */
phpdbg_stack_separate(phpdbg_param_t * param)458 PHPDBG_API void phpdbg_stack_separate(phpdbg_param_t *param) {
459 	phpdbg_param_t *stack = calloc(1, sizeof(phpdbg_param_t));
460 
461 	stack->type = STACK_PARAM;
462 	stack->next = param->next;
463 	param->next = stack;
464 	stack->top = param->top;
465 } /* }}} */
466 
phpdbg_stack_verify(const phpdbg_command_t * command,phpdbg_param_t ** stack)467 PHPDBG_API int phpdbg_stack_verify(const phpdbg_command_t *command, phpdbg_param_t **stack) {
468 	if (command) {
469 		char buffer[128] = {0,};
470 		const phpdbg_param_t *top = (stack != NULL) ? *stack : NULL;
471 		const char *arg = command->args;
472 		zend_ulong least = 0L,
473 		           received = 0L,
474 		           current = 0L;
475 		bool optional = 0;
476 
477 		/* check for arg spec */
478 		if (!(arg) || !(*arg)) {
479 			if (!top || top->type == STACK_PARAM) {
480 				return SUCCESS;
481 			}
482 
483 			phpdbg_error("The command \"%s\" expected no arguments",
484 				phpdbg_command_name(command, buffer));
485 			return FAILURE;
486 		}
487 
488 		least = 0L;
489 
490 		/* count least amount of arguments */
491 		while (arg && *arg) {
492 			if (arg[0] == '|') {
493 				break;
494 			}
495 			least++;
496 			arg++;
497 		}
498 
499 		arg = command->args;
500 
501 #define verify_arg(e, a, t) if (!(a)) { \
502 	if (!optional) { \
503 		phpdbg_error("The command \"%s\" expected %s and got nothing at parameter "ZEND_ULONG_FMT, \
504 			phpdbg_command_name(command, buffer), \
505 			(e), \
506 			current); \
507 		return FAILURE;\
508 	} \
509 } else if ((a)->type != (t)) { \
510 	phpdbg_error("The command \"%s\" expected %s and got %s at parameter "ZEND_ULONG_FMT, \
511 		phpdbg_command_name(command, buffer), \
512 		(e),\
513 		phpdbg_get_param_type((a)), \
514 		current); \
515 	return FAILURE; \
516 }
517 
518 		while (arg && *arg) {
519 			if (top && top->type == STACK_PARAM) {
520 				break;
521 			}
522 
523 			current++;
524 
525 			switch (*arg) {
526 				case '|': {
527 					current--;
528 					optional = 1;
529 					arg++;
530 				} continue;
531 
532 				case 'i': verify_arg("raw input", top, STR_PARAM); break;
533 				case 's': verify_arg("string", top, STR_PARAM); break;
534 				case 'n': verify_arg("number", top, NUMERIC_PARAM); break;
535 				case 'm': verify_arg("method", top, METHOD_PARAM); break;
536 				case 'a': verify_arg("address", top, ADDR_PARAM); break;
537 				case 'f': verify_arg("file:line", top, FILE_PARAM); break;
538 				case 'c': verify_arg("condition", top, COND_PARAM); break;
539 				case 'o': verify_arg("opcode", top, OP_PARAM); break;
540 				case 'b': verify_arg("boolean", top, NUMERIC_PARAM); break;
541 
542 				case '*': { /* do nothing */ } break;
543 			}
544 
545 			if (top) {
546 				top = top->next;
547 			} else {
548 				break;
549 			}
550 
551 			received++;
552 			arg++;
553 		}
554 
555 #undef verify_arg
556 
557 		if ((received < least)) {
558 			phpdbg_error("The command \"%s\" expected at least "ZEND_ULONG_FMT" arguments (%s) and received "ZEND_ULONG_FMT,
559 				phpdbg_command_name(command, buffer),
560 				least,
561 				command->args,
562 				received);
563 			return FAILURE;
564 		}
565 	}
566 
567 	return SUCCESS;
568 }
569 
570 /* {{{ */
phpdbg_stack_resolve(const phpdbg_command_t * commands,const phpdbg_command_t * parent,phpdbg_param_t ** top)571 PHPDBG_API const phpdbg_command_t *phpdbg_stack_resolve(const phpdbg_command_t *commands, const phpdbg_command_t *parent, phpdbg_param_t **top) {
572 	const phpdbg_command_t *command = commands;
573 	phpdbg_param_t *name = *top;
574 	const phpdbg_command_t *matched[3] = {NULL, NULL, NULL};
575 	zend_ulong matches = 0L;
576 
577 	while (command && command->name && command->handler) {
578 		if (name->len == 1 || command->name_len >= name->len) {
579 			/* match single letter alias */
580 			if (command->alias && (name->len == 1)) {
581 				if (command->alias == (*name->str)) {
582 					matched[matches] = command;
583 					matches++;
584 				}
585 			} else {
586 				/* match full, case insensitive, command name */
587 				if (strncasecmp(command->name, name->str, name->len) == SUCCESS) {
588 					if (matches < 3) {
589 						/* only allow abbreviating commands that can be aliased */
590 						if ((name->len != command->name_len && command->alias) || name->len == command->name_len) {
591 							matched[matches] = command;
592 							matches++;
593 						}
594 
595 						/* exact match */
596 						if (name->len == command->name_len) {
597 							break;
598 						}
599 					} else {
600 						break;
601 					}
602 				}
603 			}
604 		}
605 
606 		command++;
607 	}
608 
609 	switch (matches) {
610 		case 0:
611 			if (parent) {
612 				phpdbg_error("The command \"%s %s\" could not be found", parent->name, name->str);
613 			} else {
614 				phpdbg_error("The command \"%s\" could not be found", name->str);
615 			}
616 			return parent;
617 
618 		case 1:
619 			(*top) = (*top)->next;
620 
621 			command = matched[0];
622 			break;
623 
624 		default: {
625 			char *list = NULL;
626 			uint32_t it = 0;
627 			size_t pos = 0;
628 
629 			while (it < matches) {
630 				if (!list) {
631 					list = emalloc(matched[it]->name_len + 1 + (it + 1 < matches ? sizeof(", ") - 1 : 0));
632 				} else {
633 					list = erealloc(list, (pos + matched[it]->name_len) + 1 + (it + 1 < matches ? sizeof(", ") - 1 : 0));
634 				}
635 				memcpy(&list[pos], matched[it]->name, matched[it]->name_len);
636 				pos += matched[it]->name_len;
637 				if ((it + 1) < matches) {
638 					memcpy(&list[pos], ", ", sizeof(", ") - 1);
639 					pos += (sizeof(", ") - 1);
640 				}
641 
642 				list[pos] = 0;
643 				it++;
644 			}
645 
646 			/* ", " separated matches */
647 			phpdbg_error("The command \"%s\" is ambiguous, matching "ZEND_ULONG_FMT" commands (%s)", name->str, matches, list);
648 			efree(list);
649 
650 			return NULL;
651 		}
652 	}
653 
654 	if (command->subs && (*top) && ((*top)->type == STR_PARAM)) {
655 		return phpdbg_stack_resolve(command->subs, command, top);
656 	} else {
657 		return command;
658 	}
659 
660 	return NULL;
661 } /* }}} */
662 
phpdbg_internal_stack_execute(phpdbg_param_t * stack,bool allow_async_unsafe)663 static int phpdbg_internal_stack_execute(phpdbg_param_t *stack, bool allow_async_unsafe) {
664 	const phpdbg_command_t *handler = NULL;
665 	phpdbg_param_t *top = (phpdbg_param_t *) stack->next;
666 
667 	switch (top->type) {
668 		case EVAL_PARAM:
669 			phpdbg_activate_err_buf(0);
670 			phpdbg_free_err_buf();
671 			return PHPDBG_COMMAND_HANDLER(ev)(top);
672 
673 		case RUN_PARAM:
674 			if (!allow_async_unsafe) {
675 				phpdbg_error("run command is disallowed during hard interrupt");
676 			}
677 			phpdbg_activate_err_buf(0);
678 			phpdbg_free_err_buf();
679 			return PHPDBG_COMMAND_HANDLER(run)(top);
680 
681 		case SHELL_PARAM:
682 			if (!allow_async_unsafe) {
683 				phpdbg_error("sh command is disallowed during hard interrupt");
684 				return FAILURE;
685 			}
686 			phpdbg_activate_err_buf(0);
687 			phpdbg_free_err_buf();
688 			return PHPDBG_COMMAND_HANDLER(sh)(top);
689 
690 		case STR_PARAM: {
691 			handler = phpdbg_stack_resolve(phpdbg_prompt_commands, NULL, &top);
692 
693 			if (handler) {
694 				if (!allow_async_unsafe && !(handler->flags & PHPDBG_ASYNC_SAFE)) {
695 					phpdbg_error("%s command is disallowed during hard interrupt", handler->name);
696 					return FAILURE;
697 				}
698 
699 				if (phpdbg_stack_verify(handler, &top) == SUCCESS) {
700 					phpdbg_activate_err_buf(0);
701 					phpdbg_free_err_buf();
702 					return handler->handler(top);
703 				}
704 			}
705 		} return FAILURE;
706 
707 		default:
708 			phpdbg_error("The first parameter makes no sense !");
709 			return FAILURE;
710 	}
711 
712 	return SUCCESS;
713 } /* }}} */
714 
715 /* {{{ */
phpdbg_stack_execute(phpdbg_param_t * stack,bool allow_async_unsafe)716 PHPDBG_API int phpdbg_stack_execute(phpdbg_param_t *stack, bool allow_async_unsafe) {
717 	phpdbg_param_t *top = stack;
718 
719 	if (stack->type != STACK_PARAM) {
720 		phpdbg_error("The passed argument was not a stack !");
721 		return FAILURE;
722 	}
723 
724 	if (!stack->len) {
725 		phpdbg_error("The stack contains nothing !");
726 		return FAILURE;
727 	}
728 
729 	do {
730 		if (top->type == STACK_PARAM) {
731 			int result;
732 			if ((result = phpdbg_internal_stack_execute(top, allow_async_unsafe)) != SUCCESS) {
733 				return result;
734 			}
735 		}
736 	} while ((top = top->next));
737 
738 	return SUCCESS;
739 } /* }}} */
740 
phpdbg_read_input(const char * buffered)741 PHPDBG_API char *phpdbg_read_input(const char *buffered) /* {{{ */
742 {
743 	char *buffer = NULL;
744 
745 	if ((PHPDBG_G(flags) & (PHPDBG_IS_STOPPING | PHPDBG_IS_RUNNING)) != PHPDBG_IS_STOPPING) {
746 		if (buffered == NULL) {
747 #ifdef HAVE_PHPDBG_READLINE
748 			char *cmd = readline(phpdbg_get_prompt());
749 			PHPDBG_G(last_was_newline) = 1;
750 
751 			if (!cmd) {
752 				PHPDBG_G(flags) |= PHPDBG_IS_QUITTING;
753 				zend_bailout();
754 			}
755 
756 			add_history(cmd);
757 			buffer = estrdup(cmd);
758 			free(cmd);
759 #else
760 			char buf[PHPDBG_MAX_CMD];
761 			phpdbg_write("%s", phpdbg_get_prompt());
762 			phpdbg_consume_stdin_line(buf);
763 			buffer = estrdup(buf);
764 #endif
765 		} else {
766 			buffer = estrdup(buffered);
767 		}
768 	}
769 
770 	if (buffer && isspace(*buffer)) {
771 		char *trimmed = buffer;
772 		while (isspace(*trimmed))
773 			trimmed++;
774 
775 		trimmed = estrdup(trimmed);
776 		efree(buffer);
777 		buffer = trimmed;
778 	}
779 
780 	if (buffer && strlen(buffer)) {
781 		if (PHPDBG_G(buffer)) {
782 			free(PHPDBG_G(buffer));
783 		}
784 		PHPDBG_G(buffer) = strdup(buffer);
785 	} else if (PHPDBG_G(buffer)) {
786 		if (buffer) {
787 			efree(buffer);
788 		}
789 		buffer = estrdup(PHPDBG_G(buffer));
790 	}
791 
792 	return buffer;
793 } /* }}} */
794 
phpdbg_destroy_input(char ** input)795 PHPDBG_API void phpdbg_destroy_input(char **input) /*{{{ */
796 {
797 	efree(*input);
798 } /* }}} */
799 
phpdbg_ask_user_permission(const char * question)800 PHPDBG_API int phpdbg_ask_user_permission(const char *question) {
801 	char buf[PHPDBG_MAX_CMD];
802 	phpdbg_out("%s", question);
803 	phpdbg_out(" (type y or n): ");
804 
805 	while (1) {
806 		phpdbg_consume_stdin_line(buf);
807 		if ((buf[1] == '\n' || (buf[1] == '\r' && buf[2] == '\n')) && (buf[0] == 'y' || buf[0] == 'n')) {
808 			if (buf[0] == 'y') {
809 				return SUCCESS;
810 			}
811 			return FAILURE;
812 		}
813 		phpdbg_out("Please enter either y (yes) or n (no): ");
814 	}
815 
816 	return SUCCESS;
817 }
818