1 | /* src/interfaces/ecpg/preproc/ecpg.c */ |
2 | |
3 | /* Main for ecpg, the PostgreSQL embedded SQL precompiler. */ |
4 | /* Copyright (c) 1996-2019, PostgreSQL Global Development Group */ |
5 | |
6 | #include "postgres_fe.h" |
7 | |
8 | #include <unistd.h> |
9 | |
10 | #include "getopt_long.h" |
11 | |
12 | #include "preproc_extern.h" |
13 | |
14 | int ret_value = 0; |
15 | bool autocommit = false, |
16 | auto_create_c = false, |
17 | system_includes = false, |
18 | force_indicator = true, |
19 | questionmarks = false, |
20 | regression_mode = false, |
21 | auto_prepare = false; |
22 | |
23 | char *output_filename; |
24 | |
25 | enum COMPAT_MODE compat = ECPG_COMPAT_PGSQL; |
26 | |
27 | struct _include_path *include_paths = NULL; |
28 | struct cursor *cur = NULL; |
29 | struct typedefs *types = NULL; |
30 | struct _defines *defines = NULL; |
31 | |
32 | static void |
33 | help(const char *progname) |
34 | { |
35 | printf(_("%s is the PostgreSQL embedded SQL preprocessor for C programs.\n\n" ), |
36 | progname); |
37 | printf(_("Usage:\n" |
38 | " %s [OPTION]... FILE...\n\n" ), |
39 | progname); |
40 | printf(_("Options:\n" )); |
41 | printf(_(" -c automatically generate C code from embedded SQL code;\n" |
42 | " this affects EXEC SQL TYPE\n" )); |
43 | printf(_(" -C MODE set compatibility mode; MODE can be one of\n" |
44 | " \"INFORMIX\", \"INFORMIX_SE\", \"ORACLE\"\n" )); |
45 | #ifdef YYDEBUG |
46 | printf(_(" -d generate parser debug output\n" )); |
47 | #endif |
48 | printf(_(" -D SYMBOL define SYMBOL\n" )); |
49 | printf(_(" -h parse a header file, this option includes option \"-c\"\n" )); |
50 | printf(_(" -i parse system include files as well\n" )); |
51 | printf(_(" -I DIRECTORY search DIRECTORY for include files\n" )); |
52 | printf(_(" -o OUTFILE write result to OUTFILE\n" )); |
53 | printf(_(" -r OPTION specify run-time behavior; OPTION can be:\n" |
54 | " \"no_indicator\", \"prepare\", \"questionmarks\"\n" )); |
55 | printf(_(" --regression run in regression testing mode\n" )); |
56 | printf(_(" -t turn on autocommit of transactions\n" )); |
57 | printf(_(" -V, --version output version information, then exit\n" )); |
58 | printf(_(" -?, --help show this help, then exit\n" )); |
59 | printf(_("\nIf no output file is specified, the name is formed by adding .c to the\n" |
60 | "input file name, after stripping off .pgc if present.\n" )); |
61 | printf(_("\nReport bugs to <pgsql-bugs@lists.postgresql.org>.\n" )); |
62 | } |
63 | |
64 | static void |
65 | add_include_path(char *path) |
66 | { |
67 | struct _include_path *ip = include_paths, |
68 | *new; |
69 | |
70 | new = mm_alloc(sizeof(struct _include_path)); |
71 | new->path = path; |
72 | new->next = NULL; |
73 | |
74 | if (ip == NULL) |
75 | include_paths = new; |
76 | else |
77 | { |
78 | for (; ip->next != NULL; ip = ip->next); |
79 | ip->next = new; |
80 | } |
81 | } |
82 | |
83 | static void |
84 | add_preprocessor_define(char *define) |
85 | { |
86 | struct _defines *pd = defines; |
87 | char *ptr, |
88 | *define_copy = mm_strdup(define); |
89 | |
90 | defines = mm_alloc(sizeof(struct _defines)); |
91 | |
92 | /* look for = sign */ |
93 | ptr = strchr(define_copy, '='); |
94 | if (ptr != NULL) |
95 | { |
96 | char *tmp; |
97 | |
98 | /* symbol has a value */ |
99 | for (tmp = ptr - 1; *tmp == ' '; tmp--); |
100 | tmp[1] = '\0'; |
101 | defines->olddef = define_copy; |
102 | defines->newdef = ptr + 1; |
103 | } |
104 | else |
105 | { |
106 | defines->olddef = define_copy; |
107 | defines->newdef = mm_strdup("1" ); |
108 | } |
109 | defines->pertinent = true; |
110 | defines->used = NULL; |
111 | defines->next = pd; |
112 | } |
113 | |
114 | #define ECPG_GETOPT_LONG_REGRESSION 1 |
115 | int |
116 | main(int argc, char *const argv[]) |
117 | { |
118 | static struct option ecpg_options[] = { |
119 | {"regression" , no_argument, NULL, ECPG_GETOPT_LONG_REGRESSION}, |
120 | {NULL, 0, NULL, 0} |
121 | }; |
122 | |
123 | int fnr, |
124 | c, |
125 | out_option = 0; |
126 | bool verbose = false, |
127 | = false; |
128 | struct _include_path *ip; |
129 | const char *progname; |
130 | char my_exec_path[MAXPGPATH]; |
131 | char include_path[MAXPGPATH]; |
132 | |
133 | set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("ecpg" )); |
134 | |
135 | progname = get_progname(argv[0]); |
136 | |
137 | if (find_my_exec(argv[0], my_exec_path) < 0) |
138 | { |
139 | fprintf(stderr, _("%s: could not locate my own executable path\n" ), argv[0]); |
140 | return ILLEGAL_OPTION; |
141 | } |
142 | |
143 | if (argc > 1) |
144 | { |
145 | if (strcmp(argv[1], "--help" ) == 0 || strcmp(argv[1], "-?" ) == 0) |
146 | { |
147 | help(progname); |
148 | exit(0); |
149 | } |
150 | if (strcmp(argv[1], "--version" ) == 0 || strcmp(argv[1], "-V" ) == 0) |
151 | { |
152 | printf("ecpg (PostgreSQL) %s\n" , PG_VERSION); |
153 | exit(0); |
154 | } |
155 | } |
156 | |
157 | output_filename = NULL; |
158 | while ((c = getopt_long(argc, argv, "vcio:I:tD:dC:r:h" , ecpg_options, NULL)) != -1) |
159 | { |
160 | switch (c) |
161 | { |
162 | case ECPG_GETOPT_LONG_REGRESSION: |
163 | regression_mode = true; |
164 | break; |
165 | case 'o': |
166 | output_filename = mm_strdup(optarg); |
167 | if (strcmp(output_filename, "-" ) == 0) |
168 | base_yyout = stdout; |
169 | else |
170 | base_yyout = fopen(output_filename, PG_BINARY_W); |
171 | |
172 | if (base_yyout == NULL) |
173 | { |
174 | fprintf(stderr, _("%s: could not open file \"%s\": %s\n" ), |
175 | progname, output_filename, strerror(errno)); |
176 | output_filename = NULL; |
177 | } |
178 | else |
179 | out_option = 1; |
180 | break; |
181 | case 'I': |
182 | add_include_path(optarg); |
183 | break; |
184 | case 't': |
185 | autocommit = true; |
186 | break; |
187 | case 'v': |
188 | verbose = true; |
189 | break; |
190 | case 'h': |
191 | header_mode = true; |
192 | /* this must include "-c" to make sense, so fall through */ |
193 | /* FALLTHROUGH */ |
194 | case 'c': |
195 | auto_create_c = true; |
196 | break; |
197 | case 'i': |
198 | system_includes = true; |
199 | break; |
200 | case 'C': |
201 | if (pg_strcasecmp(optarg, "INFORMIX" ) == 0 || pg_strcasecmp(optarg, "INFORMIX_SE" ) == 0) |
202 | { |
203 | char pkginclude_path[MAXPGPATH]; |
204 | char informix_path[MAXPGPATH]; |
205 | |
206 | compat = (pg_strcasecmp(optarg, "INFORMIX" ) == 0) ? ECPG_COMPAT_INFORMIX : ECPG_COMPAT_INFORMIX_SE; |
207 | get_pkginclude_path(my_exec_path, pkginclude_path); |
208 | snprintf(informix_path, MAXPGPATH, "%s/informix/esql" , pkginclude_path); |
209 | add_include_path(informix_path); |
210 | } |
211 | else if (strncmp(optarg, "ORACLE" , strlen("ORACLE" )) == 0) |
212 | { |
213 | compat = ECPG_COMPAT_ORACLE; |
214 | } |
215 | else |
216 | { |
217 | fprintf(stderr, _("Try \"%s --help\" for more information.\n" ), argv[0]); |
218 | return ILLEGAL_OPTION; |
219 | } |
220 | break; |
221 | case 'r': |
222 | if (strcmp(optarg, "no_indicator" ) == 0) |
223 | force_indicator = false; |
224 | else if (strcmp(optarg, "prepare" ) == 0) |
225 | auto_prepare = true; |
226 | else if (strcmp(optarg, "questionmarks" ) == 0) |
227 | questionmarks = true; |
228 | else |
229 | { |
230 | fprintf(stderr, _("Try \"%s --help\" for more information.\n" ), argv[0]); |
231 | return ILLEGAL_OPTION; |
232 | } |
233 | break; |
234 | case 'D': |
235 | add_preprocessor_define(optarg); |
236 | break; |
237 | case 'd': |
238 | #ifdef YYDEBUG |
239 | base_yydebug = 1; |
240 | #else |
241 | fprintf(stderr, _("%s: parser debug support (-d) not available\n" ), |
242 | progname); |
243 | #endif |
244 | break; |
245 | default: |
246 | fprintf(stderr, _("Try \"%s --help\" for more information.\n" ), argv[0]); |
247 | return ILLEGAL_OPTION; |
248 | } |
249 | } |
250 | |
251 | add_include_path("." ); |
252 | add_include_path("/usr/local/include" ); |
253 | get_include_path(my_exec_path, include_path); |
254 | add_include_path(include_path); |
255 | add_include_path("/usr/include" ); |
256 | |
257 | if (verbose) |
258 | { |
259 | fprintf(stderr, |
260 | _("%s, the PostgreSQL embedded C preprocessor, version %s\n" ), |
261 | progname, PG_VERSION); |
262 | fprintf(stderr, _("EXEC SQL INCLUDE ... search starts here:\n" )); |
263 | for (ip = include_paths; ip != NULL; ip = ip->next) |
264 | fprintf(stderr, " %s\n" , ip->path); |
265 | fprintf(stderr, _("end of search list\n" )); |
266 | return 0; |
267 | } |
268 | |
269 | if (optind >= argc) /* no files specified */ |
270 | { |
271 | fprintf(stderr, _("%s: no input files specified\n" ), progname); |
272 | fprintf(stderr, _("Try \"%s --help\" for more information.\n" ), argv[0]); |
273 | return ILLEGAL_OPTION; |
274 | } |
275 | else |
276 | { |
277 | /* after the options there must not be anything but filenames */ |
278 | for (fnr = optind; fnr < argc; fnr++) |
279 | { |
280 | char *ptr2ext; |
281 | |
282 | /* If argv[fnr] is "-" we have to read from stdin */ |
283 | if (strcmp(argv[fnr], "-" ) == 0) |
284 | { |
285 | input_filename = mm_alloc(strlen("stdin" ) + 1); |
286 | strcpy(input_filename, "stdin" ); |
287 | base_yyin = stdin; |
288 | } |
289 | else |
290 | { |
291 | input_filename = mm_alloc(strlen(argv[fnr]) + 5); |
292 | strcpy(input_filename, argv[fnr]); |
293 | |
294 | /* take care of relative paths */ |
295 | ptr2ext = last_dir_separator(input_filename); |
296 | ptr2ext = (ptr2ext ? strrchr(ptr2ext, '.') : strrchr(input_filename, '.')); |
297 | |
298 | /* no extension? */ |
299 | if (ptr2ext == NULL) |
300 | { |
301 | ptr2ext = input_filename + strlen(input_filename); |
302 | |
303 | /* no extension => add .pgc or .pgh */ |
304 | ptr2ext[0] = '.'; |
305 | ptr2ext[1] = 'p'; |
306 | ptr2ext[2] = 'g'; |
307 | ptr2ext[3] = (header_mode == true) ? 'h' : 'c'; |
308 | ptr2ext[4] = '\0'; |
309 | } |
310 | |
311 | base_yyin = fopen(input_filename, PG_BINARY_R); |
312 | } |
313 | |
314 | if (out_option == 0) /* calculate the output name */ |
315 | { |
316 | if (strcmp(input_filename, "stdin" ) == 0) |
317 | base_yyout = stdout; |
318 | else |
319 | { |
320 | output_filename = mm_alloc(strlen(input_filename) + 3); |
321 | strcpy(output_filename, input_filename); |
322 | |
323 | ptr2ext = strrchr(output_filename, '.'); |
324 | /* make extension = .c resp. .h */ |
325 | ptr2ext[1] = (header_mode == true) ? 'h' : 'c'; |
326 | ptr2ext[2] = '\0'; |
327 | |
328 | base_yyout = fopen(output_filename, PG_BINARY_W); |
329 | if (base_yyout == NULL) |
330 | { |
331 | fprintf(stderr, _("%s: could not open file \"%s\": %s\n" ), |
332 | progname, output_filename, strerror(errno)); |
333 | free(output_filename); |
334 | output_filename = NULL; |
335 | free(input_filename); |
336 | continue; |
337 | } |
338 | } |
339 | } |
340 | |
341 | if (base_yyin == NULL) |
342 | fprintf(stderr, _("%s: could not open file \"%s\": %s\n" ), |
343 | progname, argv[fnr], strerror(errno)); |
344 | else |
345 | { |
346 | struct cursor *ptr; |
347 | struct _defines *defptr; |
348 | struct typedefs *typeptr; |
349 | |
350 | /* remove old cursor definitions if any are still there */ |
351 | for (ptr = cur; ptr != NULL;) |
352 | { |
353 | struct cursor *this = ptr; |
354 | struct arguments *l1, |
355 | *l2; |
356 | |
357 | free(ptr->command); |
358 | free(ptr->connection); |
359 | free(ptr->name); |
360 | for (l1 = ptr->argsinsert; l1; l1 = l2) |
361 | { |
362 | l2 = l1->next; |
363 | free(l1); |
364 | } |
365 | for (l1 = ptr->argsresult; l1; l1 = l2) |
366 | { |
367 | l2 = l1->next; |
368 | free(l1); |
369 | } |
370 | ptr = ptr->next; |
371 | free(this); |
372 | } |
373 | cur = NULL; |
374 | |
375 | /* remove non-pertinent old defines as well */ |
376 | while (defines && !defines->pertinent) |
377 | { |
378 | defptr = defines; |
379 | defines = defines->next; |
380 | |
381 | free(defptr->newdef); |
382 | free(defptr->olddef); |
383 | free(defptr); |
384 | } |
385 | |
386 | for (defptr = defines; defptr != NULL; defptr = defptr->next) |
387 | { |
388 | struct _defines *this = defptr->next; |
389 | |
390 | if (this && !this->pertinent) |
391 | { |
392 | defptr->next = this->next; |
393 | |
394 | free(this->newdef); |
395 | free(this->olddef); |
396 | free(this); |
397 | } |
398 | } |
399 | |
400 | /* and old typedefs */ |
401 | for (typeptr = types; typeptr != NULL;) |
402 | { |
403 | struct typedefs *this = typeptr; |
404 | |
405 | free(typeptr->name); |
406 | ECPGfree_struct_member(typeptr->struct_member_list); |
407 | free(typeptr->type); |
408 | typeptr = typeptr->next; |
409 | free(this); |
410 | } |
411 | types = NULL; |
412 | |
413 | /* initialize whenever structures */ |
414 | memset(&when_error, 0, sizeof(struct when)); |
415 | memset(&when_nf, 0, sizeof(struct when)); |
416 | memset(&when_warn, 0, sizeof(struct when)); |
417 | |
418 | /* and structure member lists */ |
419 | memset(struct_member_list, 0, sizeof(struct_member_list)); |
420 | |
421 | /* |
422 | * and our variable counter for out of scope cursors' |
423 | * variables |
424 | */ |
425 | ecpg_internal_var = 0; |
426 | |
427 | /* finally the actual connection */ |
428 | connection = NULL; |
429 | |
430 | /* initialize lex */ |
431 | lex_init(); |
432 | |
433 | /* we need several includes */ |
434 | /* but not if we are in header mode */ |
435 | if (regression_mode) |
436 | fprintf(base_yyout, "/* Processed by ecpg (regression mode) */\n" ); |
437 | else |
438 | fprintf(base_yyout, "/* Processed by ecpg (%s) */\n" , PG_VERSION); |
439 | |
440 | if (header_mode == false) |
441 | { |
442 | fprintf(base_yyout, "/* These include files are added by the preprocessor */\n#include <ecpglib.h>\n#include <ecpgerrno.h>\n#include <sqlca.h>\n" ); |
443 | |
444 | /* add some compatibility headers */ |
445 | if (INFORMIX_MODE) |
446 | fprintf(base_yyout, "/* Needed for informix compatibility */\n#include <ecpg_informix.h>\n" ); |
447 | |
448 | fprintf(base_yyout, "/* End of automatic include section */\n" ); |
449 | } |
450 | |
451 | if (regression_mode) |
452 | fprintf(base_yyout, "#define ECPGdebug(X,Y) ECPGdebug((X)+100,(Y))\n" ); |
453 | |
454 | output_line_number(); |
455 | |
456 | /* and parse the source */ |
457 | base_yyparse(); |
458 | |
459 | /* |
460 | * Check whether all cursors were indeed opened. It does not |
461 | * really make sense to declare a cursor but not open it. |
462 | */ |
463 | for (ptr = cur; ptr != NULL; ptr = ptr->next) |
464 | if (!(ptr->opened)) |
465 | mmerror(PARSE_ERROR, ET_WARNING, "cursor \"%s\" has been declared but not opened" , ptr->name); |
466 | |
467 | if (base_yyin != NULL && base_yyin != stdin) |
468 | fclose(base_yyin); |
469 | if (out_option == 0 && base_yyout != stdout) |
470 | fclose(base_yyout); |
471 | |
472 | /* |
473 | * If there was an error, delete the output file. |
474 | */ |
475 | if (ret_value != 0) |
476 | { |
477 | if (strcmp(output_filename, "-" ) != 0 && unlink(output_filename) != 0) |
478 | fprintf(stderr, _("could not remove output file \"%s\"\n" ), output_filename); |
479 | } |
480 | } |
481 | |
482 | if (output_filename && out_option == 0) |
483 | { |
484 | free(output_filename); |
485 | output_filename = NULL; |
486 | } |
487 | |
488 | free(input_filename); |
489 | } |
490 | } |
491 | return ret_value; |
492 | } |
493 | |