1 /*
2 * sqsh_main.c - Primary entry point into sqsh
3 *
4 * Copyright (C) 1995, 1996 by Scott C. Gray
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, write to the Free Software
18 * Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
19 *
20 * You may contact the author :
21 * e-mail: gray@voicenet.com
22 * grays@xtend-tech.com
23 * gray@xenotropic.com
24 */
25 #include <stdio.h>
26 #include <sys/ioctl.h>
27 #include <termios.h>
28 #include <pwd.h>
29 #include "sqsh_config.h"
30 #include "sqsh_job.h"
31 #include "sqsh_error.h"
32 #include "sqsh_global.h"
33 #include "sqsh_getopt.h"
34 #include "sqsh_init.h"
35 #include "sqsh_fd.h"
36 #include "sqsh_readline.h"
37 #include "sqsh_expand.h"
38 #include "sqsh_alias.h"
39 #include "sqsh_sig.h"
40 #include "sqsh_stdin.h"
41 #include "cmd.h"
42
43 /*-- Current Version --*/
44 #if !defined(lint) && !defined(__LINT__)
45 static char RCS_Id[] = "$Id: sqsh_main.c,v 1.23 2013/04/25 14:09:47 mwesdorp Exp $";
46 USE(RCS_Id)
47 #endif /* !defined(lint) */
48
49 /*-- Prototypes --*/
50 static void print_usage _ANSI_ARGS(( void ));
51
52 #define SQSH_HIDEPWD
53
54 #if defined(SQSH_HIDEPWD)
55 #define MAXPWD 256
56 static void hide_password _ANSI_ARGS(( int argc, char *argv[]));
57 #endif
58
59
60 #if defined(TIOCGWINSZ) && defined(SIGWINCH)
61 static void sigwinch_handler _ANSI_ARGS(( int, void* ));
62 #endif
63
64 /*
65 * The following structure is only used by print_usage() to describe
66 * the set of valid flags, a the arguments for that flag, and a
67 * description of that it does.
68 */
69 typedef struct sqsh_flag_st {
70 char *flag;
71 char *arg;
72 char *description;
73 } sqsh_flag_t;
74
75 /*
76 * Array of information about the flags accepted by sqsh. This
77 * is maintained in this manner so that I can have print_usage()
78 * to the format for me...I got tired of re-doing it every time
79 * I added a new flag.
80 * sqsh-2.1.6 - New parameters added and list neatly ordered.
81 */
82 static sqsh_flag_t sg_flags[] = {
83 /* Flag Argument Description
84 ----- --------- ----------------------------------- */
85 { "-a", "count", "Max. # of errors before abort" },
86 { "-A", "packet_size", "Adjust TDS packet size" },
87 { "-b", "", "Suppress banner message on startup" },
88 { "-B", "", "Turn off file buffering on startup" },
89 { "-c", "[cmdend]", "Alias for the 'go' command" },
90 { "-C", "sql", "Send sql statement to server" },
91 { "-d", "severity", "Min. severity level to display" },
92 { "-D", "database", "Change database context on startup" },
93 { "-e", "", "Echo batch prior to executing" },
94 { "-E", "editor", "Replace default editor (vi)" },
95 { "-f", "severity", "Min. severity level for failure" },
96 { "-G", "TDS version", "TDS version to use" },
97 { "-h", "", "Disable headers and footers" },
98 { "-H", "hostname", "Set the client hostname" },
99 { "-i", "filename", "Read input from file" },
100 { "-I", "interfaces", "Alternate interfaces file" },
101 { "-J", "charset", "Client character set" },
102 { "-k", "keywords", "Specify alternate keywords file" },
103 { "-K", "keytab", "Network security keytab file (DCE)" },
104 { "-l", "level|flags", "Set debugging level" },
105 { "-L", "var=value", "Set the value of a given variable" },
106 { "-m", "style", "Set display mode" },
107 { "-n", "{on|off}", "Set chained transaction mode" },
108 { "-N", "appname", "Set Application Name (sqsh)" },
109 { "-o", "filename", "Direct all output to file" },
110 { "-p", "", "Display performance stats" },
111 { "-P", "[password]", "Sybase password (NULL)" },
112 { "-Q", "query_timeout", "Query timeout period in seconds" },
113 { "-r", "[sqshrc]", "Specify name of .sqshrc" },
114 { "-R", "principal", "Network security server principal" },
115 { "-s", "colsep", "Alternate column separator (\\t)" },
116 { "-S", "server", "Name of Sybase server ($DSQUERY)" },
117 { "-t", "[filter]", "Filter batches through program" },
118 { "-T", "login_timeout", "Login timeout period in seconds" },
119 { "-U", "username", "Name of Sybase user" },
120 { "-v", "", "Display current version and exit" },
121 { "-V", "[bcdimoqru]", "Request network security services" },
122 { "-w", "width", "Adjust result display width" },
123 { "-X", "", "Enable client password encryption" },
124 { "-y", "directory", "Override value of $SYBASE" },
125 { "-z", "language", "Alternate display language" },
126 { "-Z", "[secmech]", "Network security mechanism" },
127 };
128
129 int
main(argc,argv)130 main( argc, argv )
131 int argc;
132 char *argv[];
133 {
134 int ret;
135 int exit_status;
136
137 /*-- Variables required by sqsh_getopt() --*/
138 extern int sqsh_optind;
139 extern char *sqsh_optarg;
140 char *username = NULL;
141 char *rcfile;
142 char *history;
143 char *banner;
144 char *thresh_exit;
145 char *batch_failcount;
146 char *exit_failcount;
147 char *exit_value;
148 char *term_title;
149 char str[512];
150 int ch;
151 uid_t uid;
152 struct passwd *pwd;
153 int fd;
154 int stdout_tty; /* True if stdout is a tty */
155 int stdin_tty; /* True if stdin is a tty */
156 int show_banner = True;
157 int set_width = False;
158 int read_file = False; /* True if -i supplied */
159 char *sql = NULL;
160 char *cptr;
161 int i;
162 varbuf_t *exp_buf;
163
164 /*
165 * sqsh-2.2.0 - Variables used by password handling option
166 * moved here.
167 */
168 char buf[MAXPWD];
169 char *p;
170 int fdin, fdout;
171
172
173 /*
174 * If termios.h defines TIOCGWINSZ, then we need to declare a
175 * structure in which to retrieve the current window size.
176 */
177 #if defined(TIOCGWINSZ)
178 struct winsize ws;
179 #endif
180
181 #if defined(SQSH_HIDEPWD)
182 /*
183 * If the password is passed in with the -P option then we do a little
184 * dance to hide it (i.e. we open a pipe, write the password to the pipe
185 * re-exec the program and read the pipe there.)
186 */
187 hide_password(argc, argv);
188 #endif
189
190 /*
191 * Start us our reading from regular old stdin.
192 */
193 sqsh_stdin_file( stdin );
194
195
196 /*
197 * The first thing we need to do is intialize all commands, variables,
198 * etc prior to parsing the command line. This is important because
199 * the command line flags simply alter these variables.
200 */
201 if( sqsh_init() == False ) {
202 fprintf( stderr, "%s\n", sqsh_get_errstr() );
203 sqsh_exit(255);
204 }
205
206 /*
207 * If the first argument on the command line is -r, then we want
208 * to process it before we attempt to read the .sqshrc file (since
209 * -r allows the user to specify another .sqshrc file.
210 */
211 if ( argc > 1 && strcmp( argv[1], "-r" ) == 0)
212 {
213 if (argc > 2 && *argv[2] != '-')
214 {
215 if (access( argv[2], R_OK ) == -1)
216 {
217 fprintf( stderr, "sqsh: Unable to open %s: %s\n", argv[2],
218 strerror(errno) );
219 sqsh_exit(255);
220 }
221 rcfile = argv[2];
222 env_set( g_env, "rcfile", argv[2] );
223 }
224 else
225 {
226 rcfile = NULL;
227 /* sqsh-2.1.9 - Set rcfile environment variable also to NULL */
228 env_set( g_env, "rcfile", rcfile );
229 }
230 }
231 else
232 {
233 /*
234 * We now want to read the users resource file prior to dealing
235 * with any command line options, this way we can override any
236 * value defined in the rc file with values on the command line.
237 */
238 env_get( g_env, "SQSHRC", &rcfile );
239 if (rcfile != NULL && *rcfile != '\0')
240 {
241 env_set( g_env, "rcfile", rcfile );
242 }
243 else
244 {
245 env_get( g_env, "rcfile", &rcfile );
246 }
247 }
248
249 /*
250 * Here is a nifty trick; in order to read the rc file we run the
251 * \loop command, redirecting $rcfile to its standard in, asking
252 * for it to *not* connect to the database using the -n flag.
253 */
254 if (rcfile != NULL && *rcfile != '\0')
255 {
256 /*
257 * Since we need to chop up the contents of rcfile destructively
258 * we make a backup copy of it.
259 */
260 strcpy(str, rcfile);
261 cptr = strtok( str, ":\n\t\r" );
262
263 /*
264 * Create a temporary buffer for expansion.
265 */
266 exp_buf = varbuf_create( 512 );
267 while (cptr != NULL)
268 {
269 if (sqsh_expand( cptr, exp_buf, 0 ) == False)
270 {
271 fprintf( stderr, "sqsh: Error expanding $rcfile: %s\n",
272 sqsh_get_errstr() );
273 sqsh_exit(255);
274 }
275
276 cptr = varbuf_getstr(exp_buf);
277 if (access( cptr, R_OK ) != -1)
278 {
279 env_set( g_env, "cur_rcfile", cptr );
280 if((jobset_run( g_jobset, "\\loop -n $cur_rcfile", &exit_status))
281 == -1 )
282 {
283 fprintf( stderr, "\\loop: %s\n", sqsh_get_errstr() );
284 sqsh_exit(255);
285 }
286
287 if( exit_status == CMD_FAIL )
288 {
289 fprintf( stderr, "sqsh: Error in %s\n", cptr );
290 sqsh_exit(255);
291 }
292 }
293
294 cptr = strtok( NULL, ":\n\t\r" );
295 }
296 /* sqsh-2.1.9 - Remove temporary environment variable cur_rcfile */
297 env_remove( g_env, "cur_rcfile", 0);
298 varbuf_destroy(exp_buf);
299 }
300
301 /*
302 * Parse the command line options. Note, that setting most of
303 * these variables has side effects. For example, setting
304 * $script automatically sets $interactive to 0 and redirects
305 * stdin from the script file.
306 * sqsh-2.1.6 - New parameters added to the list and cases neatly ordered
307 */
308 while ((ch = sqsh_getopt_combined( "SQSH", argc, argv,
309 "a:A:bBc;C:d:D:eE:f:G:hH:i:I:J:k:K:l:L:m:n:N:o:pP;Q:r;R:s:S:t;T:U:vV;w:Xy:z:Z;\250:" )) != EOF)
310 {
311 ret = 0;
312 switch (ch)
313 {
314 case 'a' :
315 ret = env_set( g_env, "thresh_exit", sqsh_optarg );
316 break;
317 case 'A' :
318 ret = env_set( g_env, "packet_size", sqsh_optarg );
319 break;
320 case 'b' :
321 ret = env_set( g_env, "banner", "0" );
322 break;
323 case 'B' :
324 setbuf( stdout, NULL );
325 setbuf( stderr, NULL );
326 setbuf( stdin, NULL );
327 ret = True;
328 break;
329 case 'c' :
330 /*-- Cheesy newline-go hack. Suck! --*/
331 if (sqsh_optarg == NULL || *sqsh_optarg == '\0')
332 {
333 ret = env_set( g_env, "newline_go", "1" );
334 }
335 else if (alias_add( g_alias, sqsh_optarg, "\\go" ) <= 0)
336 {
337 fprintf( stderr, "sqsh: -c: %s\n", sqsh_get_errstr() );
338 sqsh_exit(255);
339 }
340 ret = True;
341 break;
342 case 'C' :
343 ret = True;
344 sql = sqsh_optarg;
345 env_set( g_env, "sql", sqsh_optarg );
346 break;
347 case 'd' :
348 ret = env_set( g_env, "thresh_display", sqsh_optarg );
349 break;
350 case 'D' :
351 ret = env_set( g_env, "database", sqsh_optarg );
352 break;
353 case 'e' :
354 ret = env_set( g_env, "echo", "1" );
355 break;
356 case 'E' :
357 ret = env_set( g_env, "EDITOR", sqsh_optarg );
358 break;
359 case 'f' :
360 ret = env_set( g_env, "thresh_fail", sqsh_optarg );
361 break;
362 case 'G' : /* sqsh-2.1.6 */
363 ret = env_set( g_env, "tds_version", sqsh_optarg );
364 break;
365 case 'h' :
366 ret = env_set( g_env, "headers", "0" );
367
368 if (ret != -1)
369 {
370 ret = env_set( g_env, "footers", "0" );
371 }
372 break;
373 case 'H' :
374 ret = env_set( g_env, "hostname", sqsh_optarg );
375 break;
376 case 'i' :
377 read_file = True;
378 show_banner = False;
379 ret = env_set( g_env, "script", sqsh_optarg );
380 break;
381 case 'I' :
382 ret = env_set( g_env, "interfaces", sqsh_optarg );
383 break;
384 case 'J' :
385 ret = env_set( g_env, "charset", sqsh_optarg );
386 break;
387 case 'k' :
388 ret = env_set( g_env, "keyword_file", sqsh_optarg );
389 break;
390 case 'K' : /* sqsh-2.1.6 */
391 ret = env_set( g_env, "keytab_file", sqsh_optarg );
392 break;
393 case 'l' :
394 ret = env_set( g_env, "debug", sqsh_optarg );
395 break;
396 case 'L' :
397 cptr = sqsh_optarg;
398 i = 0;
399 while( i < sizeof(str) && *cptr != '\0' && *cptr != '=' )
400 str[i++] = *cptr++;
401 str[i] = '\0';
402 if( *cptr != '=' ) {
403 fprintf( stderr, "sqsh: -L: Missing '='\n" );
404 sqsh_exit(255);
405 }
406 ++cptr;
407 ret = env_set( g_env, str, cptr );
408 break;
409 case 'm' :
410 ret = env_set( g_env, "style", sqsh_optarg );
411 break;
412 case 'n' :
413 ret = env_set( g_env, "chained", sqsh_optarg );
414 break;
415 case 'N' :
416 ret = env_set( g_env, "appname", sqsh_optarg);
417 break;
418 case 'o' :
419 fflush(stdout);
420 fflush(stderr);
421 if( (fd = sqsh_open( sqsh_optarg, O_WRONLY|O_CREAT|O_TRUNC, 0 )) == -1 ||
422 sqsh_dup2( fd, fileno(stdout) ) == -1 ||
423 sqsh_dup2( fd, fileno(stderr) ) == -1 ||
424 sqsh_close( fd ) == -1 ) {
425 fprintf( stderr, "sqsh: -o: %s: %s\n", sqsh_optarg,
426 sqsh_get_errstr() );
427 sqsh_exit(255);
428 }
429 ret = True;
430 break;
431 case 'p' :
432 ret = env_set( g_env, "statistics", "1" );
433 break;
434 case 'P' :
435 /*
436 * Special case: An explicit -P'' should be treated the
437 * space as a -P without the optional argument.
438 */
439 if (sqsh_optarg == NULL || *sqsh_optarg == '\0')
440 {
441 ret = env_set( g_env, "password", NULL );
442 }
443 else
444 {
445 ret = env_set( g_env, "password", sqsh_optarg );
446
447 while( *sqsh_optarg != '\0' )
448 *sqsh_optarg++ = ' ';
449 }
450
451 break;
452 case 'Q' : /* sqsh-2.1.6 */
453 ret = env_set( g_env, "query_timeout", sqsh_optarg );
454 break;
455 case 'r' :
456 /*
457 * The alternative sqshrc file should already have been
458 * processed above. Note that the -r option should be the
459 * first option on the command line, otherwise the option
460 * is ignored, also the option and the filename should be
461 * separated by at leat a blank space.
462 */
463 ret = True;
464 break;
465 case 'R' : /* sqsh-2.1.6 */
466 ret = env_set( g_env, "principal", sqsh_optarg);
467 break;
468 case 's' :
469 ret = env_set( g_env, "colsep", sqsh_optarg );
470 break;
471 case 'S' :
472 ret = env_set( g_env, "DSQUERY", sqsh_optarg );
473 break;
474 case 't' :
475 ret = env_set( g_env, "filter", "1" );
476 if (ret == True && sqsh_optarg != NULL)
477 {
478 ret = env_set( g_env, "filter_prog", sqsh_optarg );
479 }
480 break;
481 case 'T' : /* sqsh-2.1.6 */
482 ret = env_set( g_env, "login_timeout", sqsh_optarg );
483 break;
484 case 'U' :
485 ret = env_set( g_env, "username", sqsh_optarg );
486 username = sqsh_optarg;
487 break;
488 case 'v' :
489 printf( "%s\n", g_version );
490 sqsh_exit(0);
491 break;
492 case 'V' : /* sqsh-2.1.6 */
493 if (sqsh_optarg == NULL || *sqsh_optarg == '\0')
494 ret = env_set( g_env, "secure_options", "u" );
495 else
496 ret = env_set( g_env, "secure_options", sqsh_optarg );
497 break;
498 case 'w' :
499 ret = env_set( g_env, "width", sqsh_optarg );
500 set_width = True;
501 break;
502 case 'X' :
503 ret = env_set( g_env, "encryption", "1" );
504 break;
505 case 'y' :
506 sprintf( str, "SYBASE=%s", sqsh_optarg );
507 putenv( str );
508 ret = env_set( g_env, "SYBASE", sqsh_optarg );
509 break;
510 case 'z' :
511 ret = env_set( g_env, "language", sqsh_optarg );
512 break;
513 case 'Z' : /* sqsh-2.1.6 */
514 if (sqsh_optarg == NULL || *sqsh_optarg == '\0')
515 ret = env_set( g_env, "secmech", "default");
516 else
517 ret = env_set( g_env, "secmech", sqsh_optarg );
518 break;
519 case '\250' :
520 #if defined(SQSH_HIDEPWD)
521 {
522 /*
523 * sqsh-2.1.7 - Incorporated patch by David Wood
524 * to solve a problem with pipes already in use. (Patch-id 2607434)
525 * The actual pipe file descriptors will now be passed on with the \250 option.
526 */
527 memset(buf, 0, MAXPWD);
528 if (sqsh_optarg != NULL)
529 strcpy (buf, sqsh_optarg);
530 if ((p = strchr(buf, '/')) != NULL) {
531 *p = '\0';
532 fdin = atoi(buf);
533 fdout = atoi(p+1);
534
535 memset(buf, 0, MAXPWD);
536 if (read(fdin, buf, MAXPWD-1) <= 0) {
537 fprintf(stderr, "sqsh: Error: Can't read password from pipe (filedes=%d)\n", fdin);
538 ret = False;
539 } else {
540 if (buf[0] == '\n')
541 ret = env_set( g_env, "password", NULL );
542 else
543 ret = env_set( g_env, "password", buf );
544 }
545 close(fdin);
546 close(fdout);
547 } else {
548 fprintf(stderr, "sqsh: Error: Missing pipe file descriptors for password option 250\n");
549 ret = False;
550 }
551 }
552 #endif
553 break;
554
555 default :
556 fprintf( stderr, "sqsh: %s\n", sqsh_get_errstr() );
557 print_usage();
558 sqsh_exit(255);
559 break;
560 }
561
562 /*
563 * Check the results from whichever variable we attempted
564 * to set.
565 */
566 if (ret == False)
567 {
568 fprintf( stderr, "sqsh: -%c: %s\n", ch, sqsh_get_errstr() );
569 sqsh_exit( 255 );
570 }
571 }
572
573 /*
574 * The only time that we allow extra parameters on the command line
575 * is if the -i flag is supplied. These are then passed as positional
576 * parameters to the underlying script.
577 */
578 if (read_file == False && argc != sqsh_optind)
579 {
580 print_usage();
581 sqsh_exit( 255 );
582 }
583
584 /*
585 * If we are reading from a file and there are parameters left then
586 * we want to copy them to the internal environment list.
587 */
588 if (read_file)
589 {
590 if (sqsh_optind > 1)
591 {
592 env_get (g_env, "script", &(argv[sqsh_optind-1])) ;
593 }
594 g_func_args[g_func_nargs].argc = argc - sqsh_optind + 1;
595 g_func_args[g_func_nargs].argv = &(argv[sqsh_optind-1]);
596 ++g_func_nargs;
597 }
598
599 /*
600 * Figure out if stdin or stdout is connected to a tty.
601 *
602 * sqsh-2.1.7 - Note, if a file is provided with -i, stdin will be directed
603 * to this file in \cmd_loop. So at this point, technically speaking, stdin
604 * is still connected to a tty. That's why we can't use sqsh_stdin_isatty()
605 * here as it will always return True.
606 */
607 stdout_tty = isatty( fileno(stdout) );
608 if (isatty (fileno(stdin)) && !read_file && !sql)
609 stdin_tty = True;
610 else
611 stdin_tty = False;
612 DBG(sqsh_debug(DEBUG_SCREEN,"sqsh_main: : stdin_tty: %d, stdout_tty: %d\n",
613 stdin_tty, stdout_tty));
614 if (stdin_tty && !stdout_tty)
615 {
616 fprintf( stderr, "sqsh: Error: Cannot run in interactive mode with redirected output\n" );
617 sqsh_exit( 255 );
618 }
619 if (stdin_tty && stdout_tty)
620 g_interactive = True;
621
622 #if defined(TIOCGWINSZ)
623 /*
624 * If the width hasn't been explicitly set by the user and the
625 * stdout is connected to a tty, then we want to request the
626 * current screen width from the tty.
627 */
628 if (set_width == False && stdout_tty)
629 {
630 /* Check to see if the width has been set via the sqshrc file.
631 * To do this we get the current setting - if it is != 80 then
632 * the sqshrc file had a \set width directive, which we don't want
633 * to override here.
634 */
635 char *w;
636 env_get( g_env, "width", &w);
637 if(!w || atoi(w) == 80) {
638 if (ioctl(fileno(stdout), TIOCGWINSZ, &ws ) != -1) {
639 sprintf( str, "%d", ws.ws_col );
640 env_set( g_env, "width", str );
641
642 DBG(sqsh_debug(DEBUG_SCREEN,"sqsh_main: Screen width = %d\n",
643 ws.ws_col);)
644 } else {
645 DBG(sqsh_debug(DEBUG_SCREEN,"sqsh_main: ioctl(%d,TIOCGWINSZ): %s\n",
646 (int)fileno(stdout), strerror(errno));)
647 }
648 }
649
650 #if defined(SIGWINCH)
651 if (stdout_tty)
652 {
653 sig_install( SIGWINCH, sigwinch_handler, (void*)NULL, 0 );
654 }
655 #endif /* SIGWINCH */
656 }
657 #endif /* TIOGWINSZ */
658
659 #if defined (SIGQUIT)
660 /*
661 * Disable the CTRL-\ (SIGQUIT) signal when in interactive mode.
662 */
663 if (g_interactive)
664 sig_install( SIGQUIT, SIG_H_IGN, NULL, 0 );
665 #endif /* SIGQUIT */
666
667 /*
668 * Set a TERM window title when running in interactive mode
669 * and variable term_title is set.
670 */
671 if (g_interactive)
672 {
673 env_get( g_env, "term_title", &term_title );
674 if (term_title != NULL && *term_title != '\0')
675 {
676 exp_buf = varbuf_create( 64 );
677
678 if (exp_buf == NULL)
679 {
680 fprintf( stderr, "sqsh: %s\n", sqsh_get_errstr() );
681 sqsh_exit( 255 );
682 }
683
684 if (sqsh_expand( term_title, exp_buf, 0 ) == False)
685 {
686 fprintf( stderr, "sqsh: Error expanding $term_title: %s\n",
687 sqsh_get_errstr() );
688 sqsh_exit( 255 );
689 }
690 fprintf (stdout, "%c]0;%s %s%c",
691 '\033', argv[0], varbuf_getstr( exp_buf ), '\007' );
692 varbuf_destroy( exp_buf );
693 }
694 }
695
696 /*
697 * If both input and output are connected to a tty *and* $banner
698 * is 1 and the -i flag has not been set, then we display the
699 * banner.
700 */
701 env_get( g_env, "banner", &banner );
702 if (show_banner && (banner == NULL || *banner == '1') &&
703 g_interactive)
704 {
705 printf( "%s ", g_version );
706 DBG(printf( "(DEBUG) " );)
707 printf( "%s", g_copyright );
708 printf( "\n" );
709 printf( "This is free software with ABSOLUTELY NO WARRANTY\n" );
710 printf( "For more information type '\\warranty'\n" );
711 }
712
713 /*
714 * Now, if username hasn't been explicitly set at the command
715 * line then figure out what it is by looking at the current
716 * uid.
717 */
718 env_get( g_env, "username", &username );
719
720 if (username == NULL)
721 {
722 uid = getuid();
723 pwd = getpwuid( uid );
724
725 if (pwd == NULL)
726 {
727 fprintf( stderr, "sqsh: Unable to get username for uid %d: %s\n",
728 (int)uid, strerror(errno) );
729 sqsh_exit(255);
730 }
731
732 env_set( g_env, "username", pwd->pw_name );
733 }
734
735 /*
736 * Before we go any further we need to do a little work if
737 * we are connected to a tty.
738 */
739 if (stdin_tty)
740 {
741 /*
742 * Next, we need to try to load the history file, if one is
743 * available. We ignore the results of history_load, just
744 * in case the history file doesn't exist.
745 */
746 env_get( g_env, "history", &history );
747
748 /*
749 * If a history file has been defined, then we want to
750 * expand its contents. This will allow folks to have
751 * a different history file for each server.
752 */
753 if (history != NULL && *history != '\0')
754 {
755 exp_buf = varbuf_create( 512 );
756
757 if (exp_buf == NULL)
758 {
759 fprintf( stderr, "sqsh: %s\n", sqsh_get_errstr() );
760 sqsh_exit( 255 );
761 }
762
763 if (sqsh_expand( history, exp_buf, 0 ) == False)
764 {
765 fprintf( stderr, "sqsh: Error expanding $history: %s\n",
766 sqsh_get_errstr() );
767 history_load( g_history, history );
768 }
769 else
770 {
771 history_load( g_history, varbuf_getstr( exp_buf ) );
772 }
773 varbuf_destroy( exp_buf );
774
775 sprintf( str, "%d", history_get_nbr(g_history) );
776 env_set( g_env, "histnum", str );
777 }
778
779 /*
780 * Initialize the readline "sub-system". This basically consists
781 * of installing handlers for readline keyword completion and
782 * sucking in the completion keyword list and the readline history file.
783 */
784 sqsh_readline_init();
785 }
786
787 /*
788 * If a single SQL statement was supplied on the command line
789 * then we simply want to execute the \loop -e.
790 */
791 if (sql != NULL)
792 {
793 if ((jobset_run( g_jobset, "\\loop -e \"$sql\"", &exit_status )) == -1)
794 {
795 fprintf( stderr, "\\loop: %s\n", sqsh_get_errstr() );
796 sqsh_exit( 255 );
797 }
798 }
799 else
800 {
801 /*
802 * Now that everything is configured we can go ahead and enter
803 * the read-eval-print loop. Note, it is the responsibility
804 * of the loop to establish the connection to the database.
805 */
806 if ((jobset_run( g_jobset, "\\loop $script", &exit_status )) == -1)
807 {
808 fprintf( stderr, "\\loop: %s\n", sqsh_get_errstr() );
809 sqsh_exit( 255 );
810 }
811 }
812
813 /*
814 * CMD_FAIL indicates that something went wrong internally with
815 * \loop and that it probably never even executed a command. If
816 * this is the case, then just exit with 255.
817 */
818 if( exit_status == CMD_FAIL )
819 sqsh_exit( 255 );
820
821 /*
822 * However, if \loop exited with a CMD_ABORT, then we need to check
823 * to see if the reason was because the maximum number of errors
824 * was reached.
825 */
826 if (exit_status == CMD_ABORT || exit_status == CMD_INTERRUPTED)
827 {
828 /*
829 * Retrieve the values of thresh_exit and batch_failcount. If
830 * thresh_exit is not zero, and the two variables are equal, then
831 * we need to exit with the total number of batches that failed.
832 */
833 env_get( g_env, "thresh_exit", &thresh_exit );
834 env_get( g_env, "batch_failcount", &batch_failcount );
835 if (thresh_exit != NULL && batch_failcount != NULL )
836 {
837 if( strcmp(thresh_exit,"0") != 0 &&
838 strcmp( thresh_exit, batch_failcount ) == 0 )
839 sqsh_exit( atoi(batch_failcount) );
840 }
841
842 /*
843 * Otherwise, we return the abort error code.
844 */
845 sqsh_exit( 254 );
846 }
847
848 /*
849 * This point is reached if \loop returned CMD_EXIT. Normally
850 * isql would just exit(0) here. However, we are not isql,
851 * and if exit_value is explicitly set with \exit n, then
852 * we use that return value, otherwise if exit_failcount is 1,
853 * then we exit with the total number of batches that failed.
854 */
855 env_get( g_env, "exit_value", &exit_value );
856 if ( exit_value != NULL && atoi(exit_value) != 0 )
857 sqsh_exit( atoi(exit_value) );
858
859 env_get( g_env, "exit_failcount", &exit_failcount );
860 env_get( g_env, "batch_failcount", &batch_failcount );
861 if( exit_failcount != NULL && *exit_failcount == '1' &&
862 batch_failcount != NULL ) {
863 sqsh_exit( atoi(batch_failcount) );
864 }
865
866 /*
867 * If exit_failcount is 0, then just perform a normal exit.
868 */
869 sqsh_exit( 0 );
870 /* NOTREACHED */
871 return (0);
872 }
873
874 #if defined(TIOCGWINSZ) && defined(SIGWINCH)
875
876 /*
877 * sigwinch_handler():
878 *
879 * This function is called whenever a SIGWINCH (window size change)
880 * signal is recieved during the life of sqsh. Unfortunately, this
881 * function calls quite a few functions that are known not to be
882 * signal safe, but I am willing to accept the risk. Thanks
883 * to David Whitemarsh <djw@accessio.demon.co.uk> for supplying
884 * this code.
885 */
sigwinch_handler(sig,user_data)886 static void sigwinch_handler( sig, user_data )
887 int sig;
888 void *user_data;
889 {
890 struct winsize ws;
891 char ctty_path[SQSH_MAXPATH+1];
892 int ctty_fd;
893 char num[10];
894
895 /*
896 * Unfortunately, we can recieve the SIGWINCH if our controlling
897 * terminal changes size, even though our stdout may have been
898 * redirected to a file. If this is the case, we cannot query
899 * stdout for the size, we must ask the controlling terminal.
900 */
901 if (isatty( fileno(stdout) ))
902 ctty_fd = fileno(stdout);
903 else {
904
905 /*
906 * Attempt to grab the path to our controlling tty. If we can't
907 * find it, then we have to give up.
908 */
909 if (ctermid( ctty_path ) == NULL) {
910 DBG(sqsh_debug(DEBUG_SCREEN,
911 "sigwinch: Unable to get controlling tty\n");)
912 return;
913 }
914
915 /*
916 * Now, open a file descriptor to the controlling terminal. We'll
917 * use this to query the current size.
918 */
919 if ((ctty_fd = open( ctty_path, O_RDONLY )) == -1) {
920 DBG(sqsh_debug(DEBUG_SCREEN, "sigwinch: Unable to open %s for read\n",
921 ctty_path);)
922 return;
923 }
924
925 }
926
927 if (ioctl( ctty_fd, TIOCGWINSZ, &ws ) != -1) {
928 sprintf( num, "%d", (int)ws.ws_col );
929 env_set( g_env, "width", num );
930
931 DBG(sqsh_debug(DEBUG_SCREEN, "sigwinch: Screen size changed to %d\n",
932 ws.ws_col);)
933 } else {
934 DBG(sqsh_debug(DEBUG_SCREEN, "sigwinch: ioctl(%d,TIOCGWINSZ): %s\n",
935 fileno(stdout), strerror(errno));)
936 }
937
938 /*
939 * Now, if we created a file descriptor to test the screen size
940 * then we need to destroy it.
941 */
942 if (ctty_fd != fileno(stdout))
943 close( ctty_fd );
944 }
945
946 #endif
947
948 /*
949 * print_usage():
950 *
951 * Displays the contents of the sg_flags array in a pretty manner.
952 */
print_usage()953 static void print_usage()
954 {
955 char str[40];
956 int nflags;
957 int line_len;
958 int len;
959 int middle;
960 int i, j;
961
962 /*-- Calculate the size of our array of flags --*/
963 nflags = sizeof(sg_flags) / sizeof(sqsh_flag_t);
964
965 /*
966 * The following nastyness is responsible for formatting the
967 * initial usage line. Don't bother to try to understand it...
968 * it would be easier to re-write it.
969 */
970 fprintf( stderr, "Use: sqsh" );
971 line_len = 0;
972 for( i = 0; i < nflags; i++ ) {
973 if( sg_flags[i].arg == NULL || *sg_flags[i].arg == '\0' )
974 sprintf( str, " [%s]", sg_flags[i].flag );
975 else
976 sprintf( str, " [%s %s]", sg_flags[i].flag, sg_flags[i].arg );
977
978 len = strlen( str );
979
980 if( (line_len + len) > 68 ) {
981 fprintf( stderr, "\n " );
982 line_len = 0;
983 }
984 fputs( str, stderr );
985 line_len += len;
986 }
987
988 fputs( "\n\n", stderr );
989
990 /*
991 * The splits the descriptions for each flag in our array into
992 * two nice columns.
993 */
994 middle = (nflags+1) / 2;
995 j = middle;
996 for( i = 0; i < middle; i++ ) {
997 fprintf( stderr, " %2s %-35.35s", sg_flags[i].flag,
998 sg_flags[i].description );
999 if( j < nflags ) {
1000 fprintf( stderr, " %2s %s\n", sg_flags[j].flag,
1001 sg_flags[j].description );
1002 ++j;
1003 } else {
1004 fputc( '\n', stderr );
1005 }
1006 }
1007 fputc( '\n', stderr );
1008 }
1009
1010 #if defined(SQSH_HIDEPWD)
1011 /*
1012 * sqsh-2.1.7 - Incorporated patch by David Wood
1013 * to solve a problem with pipes already in use. (Patch-id 2607434)
1014 * The actual pipe file descriptors will now be passed on with the \250 option.
1015 * sqsh-2.2.0 - Function reworked.
1016 */
hide_password(argc,argv)1017 static void hide_password (argc, argv)
1018 int argc;
1019 char *argv[];
1020 {
1021 int i, j;
1022 char buf[32];
1023 int filedes[2];
1024 char nullpwd[2];
1025 pid_t pid;
1026 char *pwd = NULL;
1027 int status;
1028
1029
1030 nullpwd[0] = '\n';
1031 nullpwd[1] = '\0';
1032
1033 /*
1034 * Loop through the list of arguments only once and skip all intermediate -P
1035 * entries, but remember the last password specified.
1036 */
1037 for (i = 0, j = 0; argv[i] != NULL; ++i)
1038 {
1039 if (*(argv[i]) == '-' && *(argv[i]+1) == 'P')
1040 {
1041 /*
1042 * New password parameter encounterd.
1043 */
1044 pwd = NULL;
1045 if (*(argv[i]+2) != '\0')
1046 {
1047 /*
1048 * Password passed on as: "sqsh -SSYBASE -Usa -Pxxxxxx" , or as -P-
1049 */
1050 pwd = (argv[i]+2);
1051 }
1052 else if ((i+1 < argc) && (*(argv[i+1]) != '-' || (*(argv[i+1]) == '-' && *(argv[i+1]+1) == '\0')))
1053 {
1054 /*
1055 * Password passed on as: "sqsh -SSYBASE -Usa -P xxxxxx" , or as -P -
1056 */
1057 pwd = argv[++i];
1058 }
1059 if (pwd == NULL || (pwd != NULL && strlen(pwd) == 0))
1060 {
1061 /*
1062 * Empty (NULL) password passed on as:
1063 * "sqsh -SSYBASE -Usa -P " or "sqsh -SSYBASE -Usa -P '' "
1064 */
1065 pwd = nullpwd;
1066 }
1067 }
1068 else
1069 argv[j++] = argv[i];
1070 }
1071
1072 /*
1073 * If pwd == NULL then no -P was specified as argument and we do not
1074 * have to hide anything from the argument list.
1075 */
1076 if (pwd == NULL)
1077 {
1078 return;
1079 }
1080
1081 /*
1082 * Create the pipe.
1083 */
1084 if (pipe (filedes) == -1)
1085 {
1086 perror ("sqsh: Error: Can't pipe()");
1087 return;
1088 }
1089 sprintf (buf, "-%c%d/%d", '\250', filedes[0], filedes[1]);
1090 argv[j++] = buf;
1091 argv[j] = NULL;
1092
1093 if ((pid = fork()) != 0)
1094 {
1095 /*
1096 * sqsh-2.2.0 - This code is executed by the parent process.
1097 * Wait for the child process to finish execution before we continue.
1098 */
1099 (void) waitpid (pid, &status, 0);
1100 /*
1101 * Re-execute ourselves in the parent process, with the modified argv[] list.
1102 */
1103 if (WIFEXITED(status) != 0 && WEXITSTATUS(status) == 0)
1104 (void) execvp (argv[0], argv);
1105 /* Not reached */
1106 else
1107 {
1108 fprintf (stderr, "sqsh: Error: Processing of function hide_password failed unexpectedly\n");
1109 sqsh_exit (255);
1110 }
1111 }
1112 else
1113 {
1114 /*
1115 * The child process writes the password to the pipe, closes the pipe
1116 * and exits.
1117 */
1118 if ((int) write (filedes[1], pwd, strlen(pwd)) != (int) strlen(pwd))
1119 {
1120 fprintf (stderr, "sqsh: Error: Failed to write password to pipe (filedes=%d)\n", filedes[1]);
1121 sqsh_exit (255);
1122 }
1123 (void) close (filedes[0]);
1124 (void) close (filedes[1]);
1125 sqsh_exit (0);
1126 }
1127 }
1128
1129 #endif
1130
1131