xref: /PHP-7.2/ext/pdo/pdo_sql_parser.c (revision b65abecf)
1 /* Generated by re2c 0.16 */
2 #line 1 "ext/pdo/pdo_sql_parser.re"
3 /*
4   +----------------------------------------------------------------------+
5   | PHP Version 7                                                        |
6   +----------------------------------------------------------------------+
7   | Copyright (c) 1997-2018 The PHP Group                                |
8   +----------------------------------------------------------------------+
9   | This source file is subject to version 3.01 of the PHP license,      |
10   | that is bundled with this package in the file LICENSE, and is        |
11   | available through the world-wide-web at the following url:           |
12   | http://www.php.net/license/3_01.txt                                  |
13   | If you did not receive a copy of the PHP license and are unable to   |
14   | obtain it through the world-wide-web, please send a note to          |
15   | license@php.net so we can mail you a copy immediately.               |
16   +----------------------------------------------------------------------+
17   | Author: George Schlossnagle <george@omniti.com>                      |
18   +----------------------------------------------------------------------+
19 */
20 
21 /* $Id$ */
22 
23 #include "php.h"
24 #include "php_pdo_driver.h"
25 #include "php_pdo_int.h"
26 
27 #define PDO_PARSER_TEXT 1
28 #define PDO_PARSER_BIND 2
29 #define PDO_PARSER_BIND_POS 3
30 #define PDO_PARSER_EOI 4
31 
32 #define RET(i) {s->cur = cursor; return i; }
33 #define SKIP_ONE(i) {s->cur = s->tok + 1; return i; }
34 
35 #define YYCTYPE         unsigned char
36 #define YYCURSOR        cursor
37 #define YYLIMIT         s->end
38 #define YYMARKER        s->ptr
39 #define YYFILL(n)		{ RET(PDO_PARSER_EOI); }
40 
41 typedef struct Scanner {
42 	char 	*ptr, *cur, *tok, *end;
43 } Scanner;
44 
scan(Scanner * s)45 static int scan(Scanner *s)
46 {
47 	char *cursor = s->cur;
48 
49 	s->tok = cursor;
50 	#line 55 "ext/pdo/pdo_sql_parser.re"
51 
52 
53 
54 #line 55 "ext/pdo/pdo_sql_parser.c"
55 {
56 	YYCTYPE yych;
57 	if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2);
58 	yych = *YYCURSOR;
59 	switch (yych) {
60 	case 0x00:	goto yy2;
61 	case '"':	goto yy6;
62 	case '\'':	goto yy8;
63 	case '(':
64 	case ')':
65 	case '*':
66 	case '+':
67 	case ',':
68 	case '.':	goto yy9;
69 	case '-':	goto yy10;
70 	case '/':	goto yy11;
71 	case ':':	goto yy12;
72 	case '?':	goto yy13;
73 	default:	goto yy3;
74 	}
75 yy2:
76 	YYCURSOR = YYMARKER;
77 	goto yy7;
78 yy3:
79 	++YYCURSOR;
80 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
81 	yych = *YYCURSOR;
82 	switch (yych) {
83 	case 0x00:
84 	case '"':
85 	case '\'':
86 	case '(':
87 	case ')':
88 	case '*':
89 	case '+':
90 	case ',':
91 	case '-':
92 	case '.':
93 	case '/':
94 	case ':':
95 	case '?':	goto yy5;
96 	default:	goto yy3;
97 	}
98 yy5:
99 #line 65 "ext/pdo/pdo_sql_parser.re"
100 	{ RET(PDO_PARSER_TEXT); }
101 #line 102 "ext/pdo/pdo_sql_parser.c"
102 yy6:
103 	yych = *(YYMARKER = ++YYCURSOR);
104 	if (yych >= 0x01) goto yy16;
105 yy7:
106 #line 63 "ext/pdo/pdo_sql_parser.re"
107 	{ SKIP_ONE(PDO_PARSER_TEXT); }
108 #line 109 "ext/pdo/pdo_sql_parser.c"
109 yy8:
110 	yych = *(YYMARKER = ++YYCURSOR);
111 	if (yych <= 0x00) goto yy7;
112 	goto yy21;
113 yy9:
114 	yych = *++YYCURSOR;
115 	goto yy7;
116 yy10:
117 	yych = *++YYCURSOR;
118 	switch (yych) {
119 	case '-':	goto yy25;
120 	default:	goto yy7;
121 	}
122 yy11:
123 	yych = *(YYMARKER = ++YYCURSOR);
124 	switch (yych) {
125 	case '*':	goto yy28;
126 	default:	goto yy7;
127 	}
128 yy12:
129 	yych = *++YYCURSOR;
130 	switch (yych) {
131 	case '0':
132 	case '1':
133 	case '2':
134 	case '3':
135 	case '4':
136 	case '5':
137 	case '6':
138 	case '7':
139 	case '8':
140 	case '9':
141 	case 'A':
142 	case 'B':
143 	case 'C':
144 	case 'D':
145 	case 'E':
146 	case 'F':
147 	case 'G':
148 	case 'H':
149 	case 'I':
150 	case 'J':
151 	case 'K':
152 	case 'L':
153 	case 'M':
154 	case 'N':
155 	case 'O':
156 	case 'P':
157 	case 'Q':
158 	case 'R':
159 	case 'S':
160 	case 'T':
161 	case 'U':
162 	case 'V':
163 	case 'W':
164 	case 'X':
165 	case 'Y':
166 	case 'Z':
167 	case '_':
168 	case 'a':
169 	case 'b':
170 	case 'c':
171 	case 'd':
172 	case 'e':
173 	case 'f':
174 	case 'g':
175 	case 'h':
176 	case 'i':
177 	case 'j':
178 	case 'k':
179 	case 'l':
180 	case 'm':
181 	case 'n':
182 	case 'o':
183 	case 'p':
184 	case 'q':
185 	case 'r':
186 	case 's':
187 	case 't':
188 	case 'u':
189 	case 'v':
190 	case 'w':
191 	case 'x':
192 	case 'y':
193 	case 'z':	goto yy30;
194 	case ':':	goto yy33;
195 	default:	goto yy7;
196 	}
197 yy13:
198 	++YYCURSOR;
199 	switch ((yych = *YYCURSOR)) {
200 	case '?':	goto yy36;
201 	default:	goto yy14;
202 	}
203 yy14:
204 #line 62 "ext/pdo/pdo_sql_parser.re"
205 	{ RET(PDO_PARSER_BIND_POS); }
206 #line 207 "ext/pdo/pdo_sql_parser.c"
207 yy15:
208 	++YYCURSOR;
209 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
210 	yych = *YYCURSOR;
211 yy16:
212 	switch (yych) {
213 	case 0x00:	goto yy2;
214 	case '"':	goto yy17;
215 	case '\\':	goto yy19;
216 	default:	goto yy15;
217 	}
218 yy17:
219 	++YYCURSOR;
220 #line 58 "ext/pdo/pdo_sql_parser.re"
221 	{ RET(PDO_PARSER_TEXT); }
222 #line 223 "ext/pdo/pdo_sql_parser.c"
223 yy19:
224 	++YYCURSOR;
225 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
226 	yych = *YYCURSOR;
227 	if (yych <= 0x00) goto yy2;
228 	goto yy15;
229 yy20:
230 	++YYCURSOR;
231 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
232 	yych = *YYCURSOR;
233 yy21:
234 	switch (yych) {
235 	case 0x00:	goto yy2;
236 	case '\'':	goto yy22;
237 	case '\\':	goto yy24;
238 	default:	goto yy20;
239 	}
240 yy22:
241 	++YYCURSOR;
242 #line 59 "ext/pdo/pdo_sql_parser.re"
243 	{ RET(PDO_PARSER_TEXT); }
244 #line 245 "ext/pdo/pdo_sql_parser.c"
245 yy24:
246 	++YYCURSOR;
247 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
248 	yych = *YYCURSOR;
249 	if (yych <= 0x00) goto yy2;
250 	goto yy20;
251 yy25:
252 	++YYCURSOR;
253 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
254 	yych = *YYCURSOR;
255 	switch (yych) {
256 	case '\n':
257 	case '\r':	goto yy27;
258 	default:	goto yy25;
259 	}
260 yy27:
261 #line 64 "ext/pdo/pdo_sql_parser.re"
262 	{ RET(PDO_PARSER_TEXT); }
263 #line 264 "ext/pdo/pdo_sql_parser.c"
264 yy28:
265 	++YYCURSOR;
266 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
267 	yych = *YYCURSOR;
268 	switch (yych) {
269 	case '*':	goto yy38;
270 	default:	goto yy28;
271 	}
272 yy30:
273 	++YYCURSOR;
274 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
275 	yych = *YYCURSOR;
276 	switch (yych) {
277 	case '0':
278 	case '1':
279 	case '2':
280 	case '3':
281 	case '4':
282 	case '5':
283 	case '6':
284 	case '7':
285 	case '8':
286 	case '9':
287 	case 'A':
288 	case 'B':
289 	case 'C':
290 	case 'D':
291 	case 'E':
292 	case 'F':
293 	case 'G':
294 	case 'H':
295 	case 'I':
296 	case 'J':
297 	case 'K':
298 	case 'L':
299 	case 'M':
300 	case 'N':
301 	case 'O':
302 	case 'P':
303 	case 'Q':
304 	case 'R':
305 	case 'S':
306 	case 'T':
307 	case 'U':
308 	case 'V':
309 	case 'W':
310 	case 'X':
311 	case 'Y':
312 	case 'Z':
313 	case '_':
314 	case 'a':
315 	case 'b':
316 	case 'c':
317 	case 'd':
318 	case 'e':
319 	case 'f':
320 	case 'g':
321 	case 'h':
322 	case 'i':
323 	case 'j':
324 	case 'k':
325 	case 'l':
326 	case 'm':
327 	case 'n':
328 	case 'o':
329 	case 'p':
330 	case 'q':
331 	case 'r':
332 	case 's':
333 	case 't':
334 	case 'u':
335 	case 'v':
336 	case 'w':
337 	case 'x':
338 	case 'y':
339 	case 'z':	goto yy30;
340 	default:	goto yy32;
341 	}
342 yy32:
343 #line 61 "ext/pdo/pdo_sql_parser.re"
344 	{ RET(PDO_PARSER_BIND); }
345 #line 346 "ext/pdo/pdo_sql_parser.c"
346 yy33:
347 	++YYCURSOR;
348 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
349 	yych = *YYCURSOR;
350 	switch (yych) {
351 	case ':':	goto yy33;
352 	default:	goto yy35;
353 	}
354 yy35:
355 #line 60 "ext/pdo/pdo_sql_parser.re"
356 	{ RET(PDO_PARSER_TEXT); }
357 #line 358 "ext/pdo/pdo_sql_parser.c"
358 yy36:
359 	++YYCURSOR;
360 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
361 	yych = *YYCURSOR;
362 	switch (yych) {
363 	case '?':	goto yy36;
364 	default:	goto yy35;
365 	}
366 yy38:
367 	++YYCURSOR;
368 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
369 	yych = *YYCURSOR;
370 	switch (yych) {
371 	case '*':	goto yy38;
372 	case '/':	goto yy40;
373 	default:	goto yy28;
374 	}
375 yy40:
376 	++YYCURSOR;
377 	yych = *YYCURSOR;
378 	goto yy27;
379 }
380 #line 66 "ext/pdo/pdo_sql_parser.re"
381 
382 }
383 
384 struct placeholder {
385 	char *pos;
386 	size_t len;
387 	int bindno;
388 	size_t qlen;		/* quoted length of value */
389 	char *quoted;	/* quoted value */
390 	int freeq;
391 	struct placeholder *next;
392 };
393 
free_param_name(zval * el)394 static void free_param_name(zval *el) {
395 	efree(Z_PTR_P(el));
396 }
397 
pdo_parse_params(pdo_stmt_t * stmt,char * inquery,size_t inquery_len,char ** outquery,size_t * outquery_len)398 PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len,
399 	char **outquery, size_t *outquery_len)
400 {
401 	Scanner s;
402 	char *ptr, *newbuffer;
403 	int t;
404 	uint32_t bindno = 0;
405 	int ret = 0;
406 	size_t newbuffer_len;
407 	HashTable *params;
408 	struct pdo_bound_param_data *param;
409 	int query_type = PDO_PLACEHOLDER_NONE;
410 	struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL;
411 
412 	ptr = *outquery;
413 	s.cur = inquery;
414 	s.end = inquery + inquery_len + 1;
415 
416 	/* phase 1: look for args */
417 	while((t = scan(&s)) != PDO_PARSER_EOI) {
418 		if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS) {
419 			if (t == PDO_PARSER_BIND) {
420 				int len = s.cur - s.tok;
421 				if ((inquery < (s.cur - len)) && isalnum(*(s.cur - len - 1))) {
422 					continue;
423 				}
424 				query_type |= PDO_PLACEHOLDER_NAMED;
425 			} else {
426 				query_type |= PDO_PLACEHOLDER_POSITIONAL;
427 			}
428 
429 			plc = emalloc(sizeof(*plc));
430 			memset(plc, 0, sizeof(*plc));
431 			plc->next = NULL;
432 			plc->pos = s.tok;
433 			plc->len = s.cur - s.tok;
434 			plc->bindno = bindno++;
435 
436 			if (placetail) {
437 				placetail->next = plc;
438 			} else {
439 				placeholders = plc;
440 			}
441 			placetail = plc;
442 		}
443 	}
444 
445 	if (bindno == 0) {
446 		/* nothing to do; good! */
447 		return 0;
448 	}
449 
450 	/* did the query make sense to me? */
451 	if (query_type == (PDO_PLACEHOLDER_NAMED|PDO_PLACEHOLDER_POSITIONAL)) {
452 		/* they mixed both types; punt */
453 		pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "mixed named and positional parameters");
454 		ret = -1;
455 		goto clean_up;
456 	}
457 
458 	if (stmt->supports_placeholders == query_type && !stmt->named_rewrite_template) {
459 		/* query matches native syntax */
460 		ret = 0;
461 		goto clean_up;
462 	}
463 
464 	if (stmt->named_rewrite_template) {
465 		/* magic/hack.
466 		 * We we pretend that the query was positional even if
467 		 * it was named so that we fall into the
468 		 * named rewrite case below.  Not too pretty,
469 		 * but it works. */
470 		query_type = PDO_PLACEHOLDER_POSITIONAL;
471 	}
472 
473 	params = stmt->bound_params;
474 
475 	/* Do we have placeholders but no bound params */
476 	if (bindno && !params && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
477 		pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "no parameters were bound");
478 		ret = -1;
479 		goto clean_up;
480 	}
481 
482 	if (params && bindno != zend_hash_num_elements(params) && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
483 		/* extra bit of validation for instances when same params are bound more than once */
484 		if (query_type != PDO_PLACEHOLDER_POSITIONAL && bindno > zend_hash_num_elements(params)) {
485 			int ok = 1;
486 			for (plc = placeholders; plc; plc = plc->next) {
487 				if ((param = zend_hash_str_find_ptr(params, plc->pos, plc->len)) == NULL) {
488 					ok = 0;
489 					break;
490 				}
491 			}
492 			if (ok) {
493 				goto safe;
494 			}
495 		}
496 		pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "number of bound variables does not match number of tokens");
497 		ret = -1;
498 		goto clean_up;
499 	}
500 safe:
501 	/* what are we going to do ? */
502 	if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
503 		/* query generation */
504 
505 		newbuffer_len = inquery_len;
506 
507 		/* let's quote all the values */
508 		for (plc = placeholders; plc; plc = plc->next) {
509 			if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
510 				param = zend_hash_index_find_ptr(params, plc->bindno);
511 			} else {
512 				param = zend_hash_str_find_ptr(params, plc->pos, plc->len);
513 			}
514 			if (param == NULL) {
515 				/* parameter was not defined */
516 				ret = -1;
517 				pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined");
518 				goto clean_up;
519 			}
520 			if (stmt->dbh->methods->quoter) {
521 				zval *parameter;
522 				if (Z_ISREF(param->parameter)) {
523 					parameter = Z_REFVAL(param->parameter);
524 				} else {
525 					parameter = &param->parameter;
526 				}
527 				if (param->param_type == PDO_PARAM_LOB && Z_TYPE_P(parameter) == IS_RESOURCE) {
528 					php_stream *stm;
529 
530 					php_stream_from_zval_no_verify(stm, parameter);
531 					if (stm) {
532 						zend_string *buf;
533 
534 						buf = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0);
535 						if (!buf) {
536 							buf = ZSTR_EMPTY_ALLOC();
537 						}
538 						if (!stmt->dbh->methods->quoter(stmt->dbh, ZSTR_VAL(buf), ZSTR_LEN(buf), &plc->quoted, &plc->qlen,
539 								param->param_type)) {
540 							/* bork */
541 							ret = -1;
542 							strncpy(stmt->error_code, stmt->dbh->error_code, 6);
543 							if (buf) {
544 								zend_string_release(buf);
545 							}
546 							goto clean_up;
547 						}
548 						if (buf) {
549 							zend_string_release(buf);
550 						}
551 					} else {
552 						pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource");
553 						ret = -1;
554 						goto clean_up;
555 					}
556 					plc->freeq = 1;
557 				} else {
558 					enum pdo_param_type param_type = param->param_type;
559 					zend_string *buf = NULL;
560 
561 					/* assume all types are nullable */
562 					if (Z_TYPE_P(parameter) == IS_NULL) {
563 						param_type = PDO_PARAM_NULL;
564 					}
565 
566 					switch (param_type) {
567 						case PDO_PARAM_BOOL:
568 							plc->quoted = zend_is_true(parameter) ? "1" : "0";
569 							plc->qlen = sizeof("1")-1;
570 							plc->freeq = 0;
571 							break;
572 
573 						case PDO_PARAM_INT:
574 							buf = zend_long_to_str(zval_get_long(parameter));
575 
576 							plc->qlen = ZSTR_LEN(buf);
577 							plc->quoted = estrdup(ZSTR_VAL(buf));
578 							plc->freeq = 1;
579 							break;
580 
581 						case PDO_PARAM_NULL:
582 							plc->quoted = "NULL";
583 							plc->qlen = sizeof("NULL")-1;
584 							plc->freeq = 0;
585 							break;
586 
587 						default:
588 							buf = zval_get_string(parameter);
589 							if (!stmt->dbh->methods->quoter(stmt->dbh, ZSTR_VAL(buf),
590 									ZSTR_LEN(buf), &plc->quoted, &plc->qlen,
591 									param_type)) {
592 								/* bork */
593 								ret = -1;
594 								strncpy(stmt->error_code, stmt->dbh->error_code, 6);
595 								if (buf) {
596 									zend_string_release(buf);
597 								}
598 								goto clean_up;
599 							}
600 							plc->freeq = 1;
601 					}
602 
603 					if (buf) {
604 						zend_string_release(buf);
605 					}
606 				}
607 			} else {
608 				zval *parameter;
609 				if (Z_ISREF(param->parameter)) {
610 					parameter = Z_REFVAL(param->parameter);
611 				} else {
612 					parameter = &param->parameter;
613 				}
614 				plc->quoted = Z_STRVAL_P(parameter);
615 				plc->qlen = Z_STRLEN_P(parameter);
616 			}
617 			newbuffer_len += plc->qlen;
618 		}
619 
620 rewrite:
621 		/* allocate output buffer */
622 		newbuffer = emalloc(newbuffer_len + 1);
623 		*outquery = newbuffer;
624 
625 		/* and build the query */
626 		plc = placeholders;
627 		ptr = inquery;
628 
629 		do {
630 			t = plc->pos - ptr;
631 			if (t) {
632 				memcpy(newbuffer, ptr, t);
633 				newbuffer += t;
634 			}
635 			memcpy(newbuffer, plc->quoted, plc->qlen);
636 			newbuffer += plc->qlen;
637 			ptr = plc->pos + plc->len;
638 
639 			plc = plc->next;
640 		} while (plc);
641 
642 		t = (inquery + inquery_len) - ptr;
643 		if (t) {
644 			memcpy(newbuffer, ptr, t);
645 			newbuffer += t;
646 		}
647 		*newbuffer = '\0';
648 		*outquery_len = newbuffer - *outquery;
649 
650 		ret = 1;
651 		goto clean_up;
652 
653 	} else if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
654 		/* rewrite ? to :pdoX */
655 		char *name, *idxbuf;
656 		const char *tmpl = stmt->named_rewrite_template ? stmt->named_rewrite_template : ":pdo%d";
657 		int bind_no = 1;
658 
659 		newbuffer_len = inquery_len;
660 
661 		if (stmt->bound_param_map == NULL) {
662 			ALLOC_HASHTABLE(stmt->bound_param_map);
663 			zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0);
664 		}
665 
666 		for (plc = placeholders; plc; plc = plc->next) {
667 			int skip_map = 0;
668 			char *p;
669 			name = estrndup(plc->pos, plc->len);
670 
671 			/* check if bound parameter is already available */
672 			if (!strcmp(name, "?") || (p = zend_hash_str_find_ptr(stmt->bound_param_map, name, plc->len)) == NULL) {
673 				spprintf(&idxbuf, 0, tmpl, bind_no++);
674 			} else {
675 				idxbuf = estrdup(p);
676 				skip_map = 1;
677 			}
678 
679 			plc->quoted = idxbuf;
680 			plc->qlen = strlen(plc->quoted);
681 			plc->freeq = 1;
682 			newbuffer_len += plc->qlen;
683 
684 			if (!skip_map && stmt->named_rewrite_template) {
685 				/* create a mapping */
686 				zend_hash_str_update_mem(stmt->bound_param_map, name, plc->len, idxbuf, plc->qlen + 1);
687 			}
688 
689 			/* map number to name */
690 			zend_hash_index_update_mem(stmt->bound_param_map, plc->bindno, idxbuf, plc->qlen + 1);
691 
692 			efree(name);
693 		}
694 
695 		goto rewrite;
696 
697 	} else {
698 		/* rewrite :name to ? */
699 
700 		newbuffer_len = inquery_len;
701 
702 		if (stmt->bound_param_map == NULL) {
703 			ALLOC_HASHTABLE(stmt->bound_param_map);
704 			zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0);
705 		}
706 
707 		for (plc = placeholders; plc; plc = plc->next) {
708 			char *name;
709 			name = estrndup(plc->pos, plc->len);
710 			zend_hash_index_update_mem(stmt->bound_param_map, plc->bindno, name, plc->len + 1);
711 			efree(name);
712 			plc->quoted = "?";
713 			plc->qlen = 1;
714 		}
715 
716 		goto rewrite;
717 	}
718 
719 clean_up:
720 
721 	while (placeholders) {
722 		plc = placeholders;
723 		placeholders = plc->next;
724 
725 		if (plc->freeq) {
726 			efree(plc->quoted);
727 		}
728 
729 		efree(plc);
730 	}
731 
732 	return ret;
733 }
734 
735 /*
736  * Local variables:
737  * tab-width: 4
738  * c-basic-offset: 4
739  * End:
740  * vim600: noet sw=4 ts=4 fdm=marker ft=c
741  * vim<600: noet sw=4 ts=4
742  */
743