Commit 4db04e73 authored by jani@prima.mysql.com's avatar jani@prima.mysql.com

New mysql client.

parent 2596e464
monty@donna.mysql.com jani@prima.mysql.com
sasha@mysql.sashanet.com
serg@serg.mysql.com
monty@narttu.mysql.fi
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
* Michael 'Monty' Widenius * Michael 'Monty' Widenius
* Andi Gutmans <andi@zend.com> * Andi Gutmans <andi@zend.com>
* Zeev Suraski <zeev@zend.com> * Zeev Suraski <zeev@zend.com>
* Jani Tolonen <jani@mysql.com>
* *
**/ **/
...@@ -38,7 +39,7 @@ ...@@ -38,7 +39,7 @@
#include "my_readline.h" #include "my_readline.h"
#include <signal.h> #include <signal.h>
gptr sql_alloc(unsigned size); // Don't use mysqld alloc for theese gptr sql_alloc(unsigned size); // Don't use mysqld alloc for these
void sql_element_free(void *ptr); void sql_element_free(void *ptr);
#include "sql_string.h" #include "sql_string.h"
...@@ -114,8 +115,9 @@ static bool info_flag=0,ignore_errors=0,wait_flag=0,quick=0, ...@@ -114,8 +115,9 @@ static bool info_flag=0,ignore_errors=0,wait_flag=0,quick=0,
no_rehash=0,skip_updates=0,safe_updates=0,one_database=0, no_rehash=0,skip_updates=0,safe_updates=0,one_database=0,
opt_compress=0, opt_compress=0,
vertical=0,skip_line_numbers=0,skip_column_names=0,opt_html=0, vertical=0,skip_line_numbers=0,skip_column_names=0,opt_html=0,
no_named_cmds=1; // we want this to be the default opt_nopager=1, opt_outfile=0,
static uint verbose=0,opt_silent=0,opt_mysql_port=0,opt_connect_timeout=0; no_named_cmds=1;
static uint verbose=0,opt_silent=0,opt_mysql_port=0;
static my_string opt_mysql_unix_port=0; static my_string opt_mysql_unix_port=0;
static int connect_flag=CLIENT_INTERACTIVE; static int connect_flag=CLIENT_INTERACTIVE;
static char *current_host,*current_db,*current_user=0,*opt_password=0, static char *current_host,*current_db,*current_user=0,*opt_password=0,
...@@ -124,6 +126,9 @@ static char *histfile; ...@@ -124,6 +126,9 @@ static char *histfile;
static String glob_buffer,old_buffer; static String glob_buffer,old_buffer;
static STATUS status; static STATUS status;
static ulong select_limit,max_join_size; static ulong select_limit,max_join_size;
static char default_pager[FN_REFLEN];
char pager[FN_REFLEN], outfile[FN_REFLEN];
FILE *PAGER, *OUTFILE;
#include "sslopt-vars.h" #include "sslopt-vars.h"
...@@ -131,6 +136,9 @@ static ulong select_limit,max_join_size; ...@@ -131,6 +136,9 @@ static ulong select_limit,max_join_size;
const char *default_dbug_option="d:t:o,/tmp/mysql.trace"; const char *default_dbug_option="d:t:o,/tmp/mysql.trace";
#endif #endif
void tee_fprintf(FILE *file, const char *fmt, ...);
void tee_fputs(const char *s, FILE *file);
void tee_puts(const char *s, FILE *file);
/* The names of functions that actually do the manipulation. */ /* The names of functions that actually do the manipulation. */
static int get_options(int argc,char **argv); static int get_options(int argc,char **argv);
static int com_quit(String *str,char*), static int com_quit(String *str,char*),
...@@ -139,13 +147,19 @@ static int com_quit(String *str,char*), ...@@ -139,13 +147,19 @@ static int com_quit(String *str,char*),
com_help(String *str,char*), com_clear(String *str,char*), com_help(String *str,char*), com_clear(String *str,char*),
com_connect(String *str,char*), com_status(String *str,char*), com_connect(String *str,char*), com_status(String *str,char*),
com_use(String *str,char*), com_source(String *str, char*), com_use(String *str,char*), com_source(String *str, char*),
com_rehash(String *str, char*); com_rehash(String *str, char*), com_pager(String *str, char*),
com_nopager(String *str, char*), com_tee(String *str, char*),
com_notee(String *str, char*);
static int read_lines(bool execute_commands); static int read_lines(bool execute_commands);
static int sql_connect(char *host,char *database,char *user,char *password, static int sql_connect(char *host,char *database,char *user,char *password,
uint silent); uint silent);
static int put_info(const char *str,INFO_TYPE info,uint error=0); static int put_info(const char *str,INFO_TYPE info,uint error=0);
static void safe_put_field(const char *pos,ulong length); static void safe_put_field(const char *pos,ulong length);
static void init_pager();
static void end_pager();
static void init_tee();
static void end_tee();
/* A structure which contains information on the commands this program /* A structure which contains information on the commands this program
can understand. */ can understand. */
...@@ -159,26 +173,36 @@ typedef struct { ...@@ -159,26 +173,36 @@ typedef struct {
} COMMANDS; } COMMANDS;
static COMMANDS commands[] = { static COMMANDS commands[] = {
{ "help", 'h', com_help, 0, "Display this text" }, { "help", 'h', com_help, 0, "Display this help." },
{ "?", '?', com_help, 0, "Synonym for `help'" }, { "?", '?', com_help, 0, "Synonym for `help'." },
{ "clear", 'c', com_clear, 0, "Clear command"}, { "clear", 'c', com_clear, 0, "Clear command."},
{ "connect",'r', com_connect,1, { "connect",'r', com_connect,1,
"Reconnect to the server. Optional arguments are db and host" }, "Reconnect to the server. Optional arguments are db and host." },
{ "edit", 'e', com_edit, 0, "Edit command with $EDITOR"}, { "edit", 'e', com_edit, 0, "Edit command with $EDITOR."},
{ "exit", 'q', com_quit, 0, "Exit mysql. Same as quit"},
{ "go", 'g', com_go, 0, "Send command to mysql server" },
{ "ego", 'G', com_ego, 0, { "ego", 'G', com_ego, 0,
"Send command to mysql server; Display result vertically"}, "Send command to mysql server, display result vertically."},
{ "print", 'p', com_print, 0, "Print current command" }, { "exit", 'q', com_quit, 0, "Exit mysql. Same as quit."},
{ "quit", 'q', com_quit, 0, "Quit mysql" }, { "go", 'g', com_go, 0, "Send command to mysql server." },
{ "rehash", '#', com_rehash, 0, "Rebuild completion hash" }, #ifndef __WIN__
{ "nopager",'n', com_nopager,0, "Disable pager, print to stdout." },
#endif
{ "notee", 't', com_notee, 0, "Don't write into outfile." },
#ifndef __WIN__
{ "pager", 'P', com_pager, 1,
"Set PAGER [to_pager]. Print the query results via PAGER." },
#endif
{ "print", 'p', com_print, 0, "Print current command." },
{ "quit", 'q', com_quit, 0, "Quit mysql." },
{ "rehash", '#', com_rehash, 0, "Rebuild completion hash." },
{ "source", '.', com_source, 1, { "source", '.', com_source, 1,
"Execute a SQL script file. Takes a file name as an argument"}, "Execute a SQL script file. Takes a file name as an argument."},
{ "status", 's', com_status, 0, "Get status information from the server"}, { "status", 's', com_status, 0, "Get status information from the server."},
{ "tee", 'T', com_tee, 1,
"Set outfile [to_outfile]. Append everything into given outfile." },
{ "use", 'u', com_use, 1, { "use", 'u', com_use, 1,
"Use another database. Takes database name as argument" }, "Use another database. Takes database name as argument." },
/* Get bash-like expansion for some commmands */ /* Get bash-like expansion for some commands */
{ "create table", 0, 0, 0, ""}, { "create table", 0, 0, 0, ""},
{ "create database", 0, 0, 0, ""}, { "create database", 0, 0, 0, ""},
{ "drop", 0, 0, 0, ""}, { "drop", 0, 0, 0, ""},
...@@ -231,6 +255,8 @@ int main(int argc,char *argv[]) ...@@ -231,6 +255,8 @@ int main(int argc,char *argv[])
DBUG_ENTER("main"); DBUG_ENTER("main");
DBUG_PROCESS(argv[0]); DBUG_PROCESS(argv[0]);
strmov(outfile, "\0"); // no (default) outfile, unless given at least once
strmov(pager, "stdout"); // the default, if --pager wasn't given
if (!isatty(0) || !isatty(1)) if (!isatty(0) || !isatty(1))
{ {
status.batch=1; opt_silent=1; status.batch=1; opt_silent=1;
...@@ -291,14 +317,17 @@ int main(int argc,char *argv[]) ...@@ -291,14 +317,17 @@ int main(int argc,char *argv[])
if (histfile) if (histfile)
{ {
if (verbose) if (verbose)
printf("Reading history-file %s\n",histfile); tee_fprintf(stdout, "Reading history-file %s\n",histfile);
read_history(histfile); read_history(histfile);
} }
} }
#endif #endif
sprintf(buff, "Type 'help;' or '\\h' for help. Type '\\c' to clear the buffer\n"); sprintf(buff,
"Type 'help;' or '\\h' for help. Type '\\c' to clear the buffer\n");
put_info(buff,INFO_INFO); put_info(buff,INFO_INFO);
status.exit_status=read_lines(1); // read lines and execute them status.exit_status=read_lines(1); // read lines and execute them
if (opt_outfile)
end_tee();
mysql_end(0); mysql_end(0);
#ifndef _lint #ifndef _lint
DBUG_RETURN(0); // Keep compiler happy DBUG_RETURN(0); // Keep compiler happy
...@@ -314,7 +343,7 @@ sig_handler mysql_end(int sig) ...@@ -314,7 +343,7 @@ sig_handler mysql_end(int sig)
{ {
/* write-history */ /* write-history */
if (verbose) if (verbose)
printf("Writing history-file %s\n",histfile); tee_fprintf(stdout, "Writing history-file %s\n",histfile);
write_history(histfile); write_history(histfile);
} }
batch_readline_end(status.line_buff); batch_readline_end(status.line_buff);
...@@ -334,14 +363,15 @@ sig_handler mysql_end(int sig) ...@@ -334,14 +363,15 @@ sig_handler mysql_end(int sig)
exit(status.exit_status); exit(status.exit_status);
} }
enum options {OPT_CHARSETS_DIR=256, OPT_DEFAULT_CHARSET, OPT_TIMEOUT} ; enum options {OPT_CHARSETS_DIR=256, OPT_DEFAULT_CHARSET, OPT_PAGER,
OPT_NOPAGER, OPT_TEE, OPT_NOTEE} ;
static struct option long_options[] = static struct option long_options[] =
{ {
{"i-am-a-dummy", no_argument, 0, 'U'}, {"i-am-a-dummy", no_argument, 0, 'U'},
{"batch", no_argument, 0, 'B'}, {"batch", no_argument, 0, 'B'},
{"character-sets-dir",required_argument,0, OPT_CHARSETS_DIR}, {"character-sets-dir",required_argument, 0, OPT_CHARSETS_DIR},
{"compress", no_argument, 0, 'C'}, {"compress", no_argument, 0, 'C'},
#ifndef DBUG_OFF #ifndef DBUG_OFF
{"debug", optional_argument, 0, '#'}, {"debug", optional_argument, 0, '#'},
...@@ -358,6 +388,14 @@ static struct option long_options[] = ...@@ -358,6 +388,14 @@ static struct option long_options[] =
{"ignore-spaces", no_argument, 0, 'i'}, {"ignore-spaces", no_argument, 0, 'i'},
{"no-auto-rehash",no_argument, 0, 'A'}, {"no-auto-rehash",no_argument, 0, 'A'},
{"no-named-commands", no_argument, 0, 'g'}, {"no-named-commands", no_argument, 0, 'g'},
{"no-tee", no_argument, 0, OPT_NOTEE},
#ifndef __WIN__
{"no-pager", no_argument, 0, OPT_NOPAGER},
{"nopager", no_argument, 0, OPT_NOPAGER}, /* we are kind */
{"pager", optional_argument, 0, OPT_PAGER},
#endif
{"notee", no_argument, 0, OPT_NOTEE}, /* we are kind */
{"tee", required_argument, 0, OPT_TEE},
{"one-database", no_argument, 0, 'o'}, {"one-database", no_argument, 0, 'o'},
{"password", optional_argument, 0, 'p'}, {"password", optional_argument, 0, 'p'},
#ifdef __WIN__ #ifdef __WIN__
...@@ -374,7 +412,6 @@ static struct option long_options[] = ...@@ -374,7 +412,6 @@ static struct option long_options[] =
{"socket", required_argument, 0, 'S'}, {"socket", required_argument, 0, 'S'},
#include "sslopt-longopts.h" #include "sslopt-longopts.h"
{"table", no_argument, 0, 't'}, {"table", no_argument, 0, 't'},
{"timeout", required_argument, 0, OPT_TIMEOUT},
#ifndef DONT_ALLOW_USER_CHANGE #ifndef DONT_ALLOW_USER_CHANGE
{"user", required_argument, 0, 'u'}, {"user", required_argument, 0, 'u'},
#endif #endif
...@@ -400,7 +437,7 @@ CHANGEABLE_VAR changeable_vars[] = { ...@@ -400,7 +437,7 @@ CHANGEABLE_VAR changeable_vars[] = {
static void usage(int version) static void usage(int version)
{ {
printf("%s Ver 10.12 Distrib %s, for %s (%s)\n", printf("%s Ver 11.2 Distrib %s, for %s (%s)\n",
my_progname, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE); my_progname, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE);
if (version) if (version)
return; return;
...@@ -440,14 +477,33 @@ static void usage(int version) ...@@ -440,14 +477,33 @@ static void usage(int version)
-i, --ignore-space Ignore space after function names.\n\ -i, --ignore-space Ignore space after function names.\n\
-h, --host=... Connect to host.\n\ -h, --host=... Connect to host.\n\
-H, --html Produce HTML output.\n\ -H, --html Produce HTML output.\n\
-L, --skip-line-numbers Don't write line number for errors.\n\ -L, --skip-line-numbers\n\
Don't write line number for errors.\n");
#ifndef __WIN__
printf("\
--no-pager Disable pager and print to stdout. See interactive\n\
help (\\h) also.\n");
#endif
printf("\
--no-tee Disable outfile. See interactive help (\\h) also.\n\
-n, --unbuffered Flush buffer after each query.\n\ -n, --unbuffered Flush buffer after each query.\n\
-N, --skip-column-names Don't write column names in results.\n\ -N, --skip-column-names\n\
Don't write column names in results.\n\
-O, --set-variable var=option\n\ -O, --set-variable var=option\n\
Give a variable an value. --help lists variables.\n\ Give a variable an value. --help lists variables.\n\
-o, --one-database Only update the default database. This is useful\n\ -o, --one-database Only update the default database. This is useful\n\
for skipping updates to other database in the update\n\ for skipping updates to other database in the update\n\
log.\n\ log.\n\
--tee=... Append everything into outfile. See interactive help\n\
(\\h) also. Does not work in batch mode.\n");
#ifndef __WIN__
printf("\
--pager[=...] Output type. Default is your ENV variable PAGER.\n\
Valid pagers are less, more, cat [> filename], etc.\n\
See interactive help (\\h) also. This options does\n\
not work in batch mode.\n");
#endif
printf("\
-p[password], --password[=...]\n\ -p[password], --password[=...]\n\
Password to use when connecting to server\n\ Password to use when connecting to server\n\
If password is not given it's asked from the tty.\n"); If password is not given it's asked from the tty.\n");
...@@ -492,7 +548,8 @@ static int get_options(int argc, char **argv) ...@@ -492,7 +548,8 @@ static int get_options(int argc, char **argv)
bool tty_password=0; bool tty_password=0;
set_all_changeable_vars(changeable_vars); set_all_changeable_vars(changeable_vars);
while ((c=getopt_long(argc,argv,"?ABCD:LfgGHinNoqrstTUvVwWEe:h:O:P:S:u:#::p::", while ((c=getopt_long(argc,argv,
"?ABCD:LfgGHinNoqrstTUvVwWEe:h:O:P:S:u:#::p::",
long_options, &option_index)) != EOF) long_options, &option_index)) != EOF)
{ {
switch(c) { switch(c) {
...@@ -502,6 +559,30 @@ static int get_options(int argc, char **argv) ...@@ -502,6 +559,30 @@ static int get_options(int argc, char **argv)
case OPT_CHARSETS_DIR: case OPT_CHARSETS_DIR:
charsets_dir= optarg; charsets_dir= optarg;
break; break;
case OPT_TEE:
if (!opt_outfile && strlen(optarg))
{
strmov(outfile, optarg);
opt_outfile=1;
init_tee();
}
break;
case OPT_NOTEE:
if (opt_outfile)
end_tee();
opt_outfile=0;
break;
case OPT_PAGER:
opt_nopager=0;
if (optarg)
strmov(pager, optarg);
else
strmov(pager, (char*) getenv("PAGER"));
strmov(default_pager, pager);
break;
case OPT_NOPAGER:
opt_nopager=1;
break;
case 'D': case 'D':
my_free(current_db,MYF(MY_ALLOW_ZERO_PTR)); my_free(current_db,MYF(MY_ALLOW_ZERO_PTR));
current_db=my_strdup(optarg,MYF(MY_WME)); current_db=my_strdup(optarg,MYF(MY_WME));
...@@ -546,12 +627,9 @@ static int get_options(int argc, char **argv) ...@@ -546,12 +627,9 @@ static int get_options(int argc, char **argv)
case 'p': case 'p':
if (optarg) if (optarg)
{ {
char *start=optarg;
my_free(opt_password,MYF(MY_ALLOW_ZERO_PTR)); my_free(opt_password,MYF(MY_ALLOW_ZERO_PTR));
opt_password=my_strdup(optarg,MYF(MY_FAE)); opt_password=my_strdup(optarg,MYF(MY_FAE));
while (*optarg) *optarg++= 'x'; // Destroy argument while (*optarg) *optarg++= 'x'; // Destroy argument
if (*start)
start[1]=0;
} }
else else
tty_password=1; tty_password=1;
...@@ -607,9 +685,6 @@ static int get_options(int argc, char **argv) ...@@ -607,9 +685,6 @@ static int get_options(int argc, char **argv)
opt_mysql_unix_port=my_strdup(MYSQL_NAMEDPIPE,MYF(0)); opt_mysql_unix_port=my_strdup(MYSQL_NAMEDPIPE,MYF(0));
#endif #endif
break; break;
case OPT_TIMEOUT:
opt_connect_timeout=atoi(optarg);
break;
case 'V': usage(1); exit(0); case 'V': usage(1); exit(0);
case 'I': case 'I':
case '?': case '?':
...@@ -617,11 +692,18 @@ static int get_options(int argc, char **argv) ...@@ -617,11 +692,18 @@ static int get_options(int argc, char **argv)
exit(0); exit(0);
#include "sslopt-case.h" #include "sslopt-case.h"
default: default:
fprintf(stderr,"illegal option: -%c\n",opterr); tee_fprintf(stderr,"illegal option: -%c\n",opterr);
usage(0); usage(0);
exit(1); exit(1);
} }
} }
if (status.batch) /* disable pager and outfile in this case */
{
strmov(default_pager, "stdout");
strmov(pager, "stdout");
opt_nopager=1;
opt_outfile=0;
}
if (default_charset) if (default_charset)
{ {
if (set_default_charset_by_name(default_charset, MYF(MY_WME))) if (set_default_charset_by_name(default_charset, MYF(MY_WME)))
...@@ -674,7 +756,7 @@ static int read_lines(bool execute_commands) ...@@ -674,7 +756,7 @@ static int read_lines(bool execute_commands)
else else
#ifdef __WIN__ #ifdef __WIN__
{ {
printf(glob_buffer.is_empty() ? "mysql> " : tee_fprintf(stdout, glob_buffer.is_empty() ? "mysql> " :
!in_string ? " -> " : !in_string ? " -> " :
in_string == '\'' ? in_string == '\'' ?
" '> " : " \"> "); " '> " : " \"> ");
...@@ -682,10 +764,21 @@ static int read_lines(bool execute_commands) ...@@ -682,10 +764,21 @@ static int read_lines(bool execute_commands)
line=_cgets(linebuffer); line=_cgets(linebuffer);
} }
#else #else
if (opt_outfile)
{
if (glob_buffer.is_empty())
fflush(OUTFILE);
fputs(glob_buffer.is_empty() ? "mysql> " :
!in_string ? " -> " :
in_string == '\'' ?
" '> " : " \"> ", OUTFILE);
}
line=readline((char*) (glob_buffer.is_empty() ? "mysql> " : line=readline((char*) (glob_buffer.is_empty() ? "mysql> " :
!in_string ? " -> " : !in_string ? " -> " :
in_string == '\'' ? in_string == '\'' ?
" '> " : " \"> ")); " '> " : " \"> "));
if (opt_outfile)
fprintf(OUTFILE, "%s\n", line);
#endif #endif
if (!line) // End of file if (!line) // End of file
{ {
...@@ -1060,7 +1153,7 @@ static void build_completion_hash(bool skip_rehash,bool write_info) ...@@ -1060,7 +1153,7 @@ static void build_completion_hash(bool skip_rehash,bool write_info)
{ {
if (mysql_num_rows(tables) > 0 && !opt_silent && write_info) if (mysql_num_rows(tables) > 0 && !opt_silent && write_info)
{ {
printf("\ tee_fprintf(stdout, "\
Reading table information for completion of table and column names\n\ Reading table information for completion of table and column names\n\
You can turn off this feature to get a quicker startup with -A\n\n"); You can turn off this feature to get a quicker startup with -A\n\n");
} }
...@@ -1121,7 +1214,8 @@ You can turn off this feature to get a quicker startup with -A\n\n"); ...@@ -1121,7 +1214,8 @@ You can turn off this feature to get a quicker startup with -A\n\n");
} }
} }
else else
printf("Didn't find any fields in table '%s'\n",table_row[0]); tee_fprintf(stdout,
"Didn't find any fields in table '%s'\n",table_row[0]);
i++; i++;
} }
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
...@@ -1189,14 +1283,15 @@ com_help (String *buffer __attribute__((unused)), ...@@ -1189,14 +1283,15 @@ com_help (String *buffer __attribute__((unused)),
for (i = 0; commands[i].name; i++) for (i = 0; commands[i].name; i++)
{ {
if (commands[i].func) if (commands[i].func)
printf("%s\t(\\%c)\t%s\n", commands[i].name, tee_fprintf(stdout, "%s\t(\\%c)\t%s\n", commands[i].name,
commands[i].cmd_char, commands[i].doc); commands[i].cmd_char, commands[i].doc);
} }
if (connected) if (connected)
printf("\nConnection id: %ld (Can be used with mysqladmin kill)\n\n", tee_fprintf(stdout,
"\nConnection id: %ld (Can be used with mysqladmin kill)\n\n",
mysql_thread_id(&mysql)); mysql_thread_id(&mysql));
else else
printf("Not connected! Reconnect with 'connect'!\n\n"); tee_fprintf(stdout, "Not connected! Reconnect with 'connect'!\n\n");
return 0; return 0;
} }
...@@ -1264,7 +1359,8 @@ com_go(String *buffer,char *line __attribute__((unused))) ...@@ -1264,7 +1359,8 @@ com_go(String *buffer,char *line __attribute__((unused)))
if (!mysql_real_query(&mysql,buffer->ptr(),buffer->length())) if (!mysql_real_query(&mysql,buffer->ptr(),buffer->length()))
break; break;
error=put_info(mysql_error(&mysql),INFO_ERROR, mysql_errno(&mysql)); error=put_info(mysql_error(&mysql),INFO_ERROR, mysql_errno(&mysql));
if (mysql_errno(&mysql) != CR_SERVER_GONE_ERROR || retry > 1 || status.batch) if (mysql_errno(&mysql) != CR_SERVER_GONE_ERROR || retry > 1
|| status.batch)
{ {
buffer->length(0); // Remove query on error buffer->length(0); // Remove query on error
return error; return error;
...@@ -1308,6 +1404,7 @@ com_go(String *buffer,char *line __attribute__((unused))) ...@@ -1308,6 +1404,7 @@ com_go(String *buffer,char *line __attribute__((unused)))
} }
else else
{ {
init_pager();
if (opt_html) if (opt_html)
print_table_data_html(result); print_table_data_html(result);
else if (vertical) else if (vertical)
...@@ -1320,6 +1417,7 @@ com_go(String *buffer,char *line __attribute__((unused))) ...@@ -1320,6 +1417,7 @@ com_go(String *buffer,char *line __attribute__((unused)))
(long) mysql_num_rows(result), (long) mysql_num_rows(result),
(long) mysql_num_rows(result) == 1 ? "row" : "rows", (long) mysql_num_rows(result) == 1 ? "row" : "rows",
time_buff); time_buff);
end_pager();
} }
} }
else if (mysql_affected_rows(&mysql) == ~(ulonglong) 0) else if (mysql_affected_rows(&mysql) == ~(ulonglong) 0)
...@@ -1342,6 +1440,47 @@ com_go(String *buffer,char *line __attribute__((unused))) ...@@ -1342,6 +1440,47 @@ com_go(String *buffer,char *line __attribute__((unused)))
return error; /* New command follows */ return error; /* New command follows */
} }
static void init_pager()
{
#ifndef __WIN__
if (!opt_nopager)
{
if (!(PAGER= popen(pager, "w")))
{
tee_fprintf(stdout, "popen() failed! defaulting PAGER to stdout!\n");
PAGER= stdout;
}
}
else
#endif
PAGER= stdout;
}
static void end_pager()
{
#ifndef __WIN__
if (!opt_nopager)
pclose(PAGER);
#endif
}
static void init_tee()
{
if (!(OUTFILE= my_fopen(outfile,O_APPEND | O_WRONLY | O_BINARY,MYF(MY_WME))))
{
opt_outfile=0;
init_pager();
return;
}
}
static void end_tee()
{
my_fclose(OUTFILE, MYF(0));
return;
}
static int static int
com_ego(String *buffer,char *line) com_ego(String *buffer,char *line)
{ {
...@@ -1377,35 +1516,34 @@ print_table_data(MYSQL_RES *result) ...@@ -1377,35 +1516,34 @@ print_table_data(MYSQL_RES *result)
separator.fill(separator.length()+length+2,'-'); separator.fill(separator.length()+length+2,'-');
separator.append('+'); separator.append('+');
} }
puts(separator.c_ptr()); tee_puts(separator.c_ptr(), PAGER);
if (!skip_column_names) if (!skip_column_names)
{ {
mysql_field_seek(result,0); mysql_field_seek(result,0);
(void) fputs("|",stdout); (void) tee_fputs("|", PAGER);
for (uint off=0; (field = mysql_fetch_field(result)) ; off++) for (uint off=0; (field = mysql_fetch_field(result)) ; off++)
{ {
printf(" %-*s|",field->max_length,field->name); tee_fprintf(PAGER, " %-*s|",field->max_length,field->name);
num_flag[off]= IS_NUM(field->type); num_flag[off]= IS_NUM(field->type);
} }
(void) fputc('\n',stdout); (void) tee_fputs("\n", PAGER);
puts(separator.c_ptr()); tee_puts(separator.c_ptr(), PAGER);
} }
while ((cur = mysql_fetch_row(result))) while ((cur = mysql_fetch_row(result)))
{ {
(void) fputs("|",stdout); (void) tee_fputs("|", PAGER);
mysql_field_seek(result,0); mysql_field_seek(result,0);
for (uint off=0 ; off < mysql_num_fields(result); off++) for (uint off=0 ; off < mysql_num_fields(result); off++)
{ {
field = mysql_fetch_field(result); field = mysql_fetch_field(result);
uint length=field->max_length; uint length=field->max_length;
printf(num_flag[off] ? "%*s |" : " %-*s|", tee_fprintf(PAGER, num_flag[off] ? "%*s |" : " %-*s|",
length,cur[off] ? (char*) cur[off] : "NULL"); length,cur[off] ? (char*) cur[off] : "NULL");
} }
(void) fputc('\n',stdout); (void) tee_fputs("\n", PAGER);
} }
puts(separator.c_ptr()); tee_puts(separator.c_ptr(), PAGER);
my_afree((gptr) num_flag); my_afree((gptr) num_flag);
} }
...@@ -1416,30 +1554,30 @@ print_table_data_html(MYSQL_RES *result) ...@@ -1416,30 +1554,30 @@ print_table_data_html(MYSQL_RES *result)
MYSQL_FIELD *field; MYSQL_FIELD *field;
mysql_field_seek(result,0); mysql_field_seek(result,0);
fputs("<TABLE BORDER=1><TR>",stdout); (void) tee_fputs("<TABLE BORDER=1><TR>", PAGER);
if (!skip_column_names) if (!skip_column_names)
{ {
while((field = mysql_fetch_field(result))) while((field = mysql_fetch_field(result)))
{ {
printf("<TH>%s</TH>", (field->name ? (field->name[0] ? field->name : tee_fprintf(PAGER, "<TH>%s</TH>", (field->name ?
" &nbsp; ") : (field->name[0] ? field->name :
"NULL")); " &nbsp; ") : "NULL"));
} }
puts("</TR>"); (void) tee_fputs("</TR>", PAGER);
} }
while ((cur = mysql_fetch_row(result))) while ((cur = mysql_fetch_row(result)))
{ {
fputs("<TR>",stdout); (void) tee_fputs("<TR>", PAGER);
for (uint i=0; i < mysql_num_fields(result); i++) for (uint i=0; i < mysql_num_fields(result); i++)
{ {
ulong *lengths=mysql_fetch_lengths(result); ulong *lengths=mysql_fetch_lengths(result);
fputs("<TD>",stdout); (void) tee_fputs("<TD>", PAGER);
safe_put_field(cur[i],lengths[i]); safe_put_field(cur[i],lengths[i]);
fputs("</TD>",stdout); (void) tee_fputs("</TD>", PAGER);
} }
puts("</TR>"); (void) tee_fputs("</TR>", PAGER);
} }
puts("</TABLE>"); (void) tee_fputs("</TABLE>", PAGER);
} }
...@@ -1463,13 +1601,13 @@ print_table_data_vertically(MYSQL_RES *result) ...@@ -1463,13 +1601,13 @@ print_table_data_vertically(MYSQL_RES *result)
for (uint row_count=1; (cur= mysql_fetch_row(result)); row_count++) for (uint row_count=1; (cur= mysql_fetch_row(result)); row_count++)
{ {
mysql_field_seek(result,0); mysql_field_seek(result,0);
printf("*************************** %d. row ***************************\n", tee_fprintf(PAGER,
row_count); "*************************** %d. row ***************************\n", row_count);
for (uint off=0; off < mysql_num_fields(result); off++) for (uint off=0; off < mysql_num_fields(result); off++)
{ {
field= mysql_fetch_field(result); field= mysql_fetch_field(result);
printf("%*s: ",(int) max_length,field->name); tee_fprintf(PAGER, "%*s: ",(int) max_length,field->name);
printf("%s\n",cur[off] ? (char*) cur[off] : "NULL"); tee_fprintf(PAGER, "%s\n",cur[off] ? (char*) cur[off] : "NULL");
} }
} }
} }
...@@ -1479,11 +1617,11 @@ static void ...@@ -1479,11 +1617,11 @@ static void
safe_put_field(const char *pos,ulong length) safe_put_field(const char *pos,ulong length)
{ {
if (!pos) if (!pos)
fputs("NULL",stdout); tee_fputs("NULL", PAGER);
else else
{ {
if (opt_raw_data) if (opt_raw_data)
fputs(pos,stdout); tee_fputs(pos, PAGER);
else for (const char *end=pos+length ; pos != end ; pos++) else for (const char *end=pos+length ; pos != end ; pos++)
{ {
#ifdef USE_MB #ifdef USE_MB
...@@ -1491,21 +1629,21 @@ safe_put_field(const char *pos,ulong length) ...@@ -1491,21 +1629,21 @@ safe_put_field(const char *pos,ulong length)
if (use_mb(default_charset_info) && if (use_mb(default_charset_info) &&
(l = my_ismbchar(default_charset_info, pos, end))) { (l = my_ismbchar(default_charset_info, pos, end))) {
while (l--) while (l--)
putchar(*pos++); tee_fputs((const char *) *pos++, PAGER);
pos--; pos--;
continue; continue;
} }
#endif #endif
if (!*pos) if (!*pos)
fputs("\\0",stdout); // This makes everything hard tee_fputs("\\0", PAGER); // This makes everything hard
else if (*pos == '\t') else if (*pos == '\t')
fputs("\\t",stdout); // This would destroy tab format tee_fputs("\\t", PAGER); // This would destroy tab format
else if (*pos == '\n') else if (*pos == '\n')
fputs("\\n",stdout); // This too tee_fputs("\\n", PAGER); // This too
else if (*pos == '\\') else if (*pos == '\\')
fputs("\\\\",stdout); tee_fputs("\\\\", PAGER);
else else
putchar(*pos); tee_fputs(pos, PAGER);
} }
} }
} }
...@@ -1524,10 +1662,10 @@ print_tab_data(MYSQL_RES *result) ...@@ -1524,10 +1662,10 @@ print_tab_data(MYSQL_RES *result)
while ((field = mysql_fetch_field(result))) while ((field = mysql_fetch_field(result)))
{ {
if (first++) if (first++)
(void) fputc('\t',stdout); (void) tee_fputs("\t", PAGER);
(void) fputs(field->name,stdout); (void) tee_fputs(field->name, PAGER);
} }
(void) fputc('\n',stdout); (void) tee_fputs("\n", PAGER);
} }
while ((cur = mysql_fetch_row(result))) while ((cur = mysql_fetch_row(result)))
{ {
...@@ -1535,11 +1673,119 @@ print_tab_data(MYSQL_RES *result) ...@@ -1535,11 +1673,119 @@ print_tab_data(MYSQL_RES *result)
safe_put_field(cur[0],lengths[0]); safe_put_field(cur[0],lengths[0]);
for (uint off=1 ; off < mysql_num_fields(result); off++) for (uint off=1 ; off < mysql_num_fields(result); off++)
{ {
(void) fputc('\t',stdout); (void) tee_fputs("\t", PAGER);
safe_put_field(cur[off],lengths[off]); safe_put_field(cur[off],lengths[off]);
} }
(void) fputc('\n',stdout); (void) tee_fputs("\n", PAGER);
}
}
static int
com_tee(String *buffer, char *line __attribute__((unused)))
{
char file_name[FN_REFLEN], *end, *param;
if (status.batch)
return 0;
while (isspace(*line))
line++;
if (!(param = strchr(line, ' '))) // if outfile wasn't given, use the default
{
if (!strlen(outfile))
{
printf("No previous outfile available, you must give the filename!\n");
opt_outfile=0;
return 0;
}
}
else
{
while (isspace(*param))
param++;
end=strmake(file_name, param, sizeof(file_name)-1);
while (end > file_name && (isspace(end[-1]) || iscntrl(end[-1])))
end--;
end[0]=0;
strmov(outfile, file_name);
}
if (!strlen(outfile))
{
printf("No outfile specified!\n");
return 0;
}
if (!opt_outfile)
{
init_tee();
opt_outfile=1;
}
tee_fprintf(stdout, "Outfile '%s' is in use now.\n", outfile);
return 0;
}
static int
com_notee(String *buffer __attribute__((unused)),
char *line __attribute__((unused)))
{
if (opt_outfile)
end_tee();
opt_outfile=0;
tee_fprintf(stdout, "Outfile disabled.\n");
return 0;
}
static int
com_pager(String *buffer, char *line __attribute__((unused)))
{
char pager_name[FN_REFLEN], *end, *param;
if (status.batch)
return 0;
#ifdef __WIN__
tee_fprintf(stdout, "Sorry, this command is not available in Windows.\n");
return 0;
#endif
/* Skip space from file name */
while (isspace(*line))
line++;
if (!(param = strchr(line, ' '))) // if pager was not given, use the default
{
if (!strlen(default_pager))
{
tee_fprintf(stdout, "Default pager wasn't available, using stdout.\n");
opt_nopager=1;
strmov(pager, "stdout");
PAGER= stdout;
return 0;
}
strmov(pager, default_pager);
}
else
{
while (isspace(*param))
param++;
end=strmake(pager_name, param, sizeof(pager_name)-1);
while (end > pager_name && (isspace(end[-1]) || iscntrl(end[-1])))
end--;
end[0]=0;
strmov(pager, pager_name);
} }
opt_nopager=0;
tee_fprintf(stdout, "PAGER set to %s\n", pager);
return 0;
}
static int
com_nopager(String *buffer __attribute__((unused)),
char *line __attribute__((unused)))
{
#ifdef __WIN__
tee_fprintf(stdout, "This command has no function in Windows.\n");
return 0;
#endif
strmov(pager, "stdout");
opt_nopager=1;
tee_fprintf(stdout, "PAGER set to stdout\n");
return 0;
} }
...@@ -1610,11 +1856,11 @@ com_rehash(String *buffer __attribute__((unused)), ...@@ -1610,11 +1856,11 @@ com_rehash(String *buffer __attribute__((unused)),
static int static int
com_print(String *buffer,char *line __attribute__((unused))) com_print(String *buffer,char *line __attribute__((unused)))
{ {
puts("--------------"); tee_puts("--------------", stdout);
(void) fputs(buffer->c_ptr(),stdout); (void) tee_fputs(buffer->c_ptr(), stdout);
if (!buffer->length() || (*buffer)[buffer->length()-1] != '\n') if (!buffer->length() || (*buffer)[buffer->length()-1] != '\n')
putchar('\n'); tee_fputs("\n", stdout);
puts("--------------\n"); tee_puts("--------------\n", stdout);
return 0; /* If empty buffer */ return 0; /* If empty buffer */
} }
...@@ -1676,8 +1922,9 @@ static int com_source(String *buffer, char *line) ...@@ -1676,8 +1922,9 @@ static int com_source(String *buffer, char *line)
/* Skip space from file name */ /* Skip space from file name */
while (isspace(*line)) while (isspace(*line))
line++; line++;
if (!(param = strchr(line, ' '))) // Skipp command name if (!(param = strchr(line, ' '))) // Skip command name
return put_info("Usage: \\. <filename> | source <filename>", INFO_ERROR, 0); return put_info("Usage: \\. <filename> | source <filename>",
INFO_ERROR, 0);
while (isspace(*param)) while (isspace(*param))
param++; param++;
end=strmake(source_name,param,sizeof(source_name)-1); end=strmake(source_name,param,sizeof(source_name)-1);
...@@ -1727,7 +1974,7 @@ com_use(String *buffer __attribute__((unused)), char *line) ...@@ -1727,7 +1974,7 @@ com_use(String *buffer __attribute__((unused)), char *line)
strnmov(buff,line,sizeof(buff)-1); // Don't destroy history strnmov(buff,line,sizeof(buff)-1); // Don't destroy history
if (buff[0] == '\\') // Short command if (buff[0] == '\\') // Short command
buff[1]=' '; buff[1]=' ';
tmp=(char *) strtok(buff," \t;"); // Skipp connect command tmp=(char *) strtok(buff," \t;"); // Skip connect command
if (!tmp || !(tmp=(char *) strtok(NullS," \t;"))) if (!tmp || !(tmp=(char *) strtok(NullS," \t;")))
{ {
put_info("USE must be followed by a database name",INFO_ERROR); put_info("USE must be followed by a database name",INFO_ERROR);
...@@ -1779,9 +2026,6 @@ sql_real_connect(char *host,char *database,char *user,char *password, ...@@ -1779,9 +2026,6 @@ sql_real_connect(char *host,char *database,char *user,char *password,
connected= 0; connected= 0;
} }
mysql_init(&mysql); mysql_init(&mysql);
if (opt_connect_timeout)
mysql_options(&mysql,MYSQL_OPT_CONNECT_TIMEOUT,
(char*) &opt_connect_timeout);
if (opt_compress) if (opt_compress)
mysql_options(&mysql,MYSQL_OPT_COMPRESS,NullS); mysql_options(&mysql,MYSQL_OPT_COMPRESS,NullS);
#ifdef HAVE_OPENSSL #ifdef HAVE_OPENSSL
...@@ -1832,7 +2076,7 @@ sql_connect(char *host,char *database,char *user,char *password,uint silent) ...@@ -1832,7 +2076,7 @@ sql_connect(char *host,char *database,char *user,char *password,uint silent)
{ {
if (count) if (count)
{ {
fputs("\n",stderr); tee_fputs("\n", stderr);
(void) fflush(stderr); (void) fflush(stderr);
} }
return error; return error;
...@@ -1842,7 +2086,7 @@ sql_connect(char *host,char *database,char *user,char *password,uint silent) ...@@ -1842,7 +2086,7 @@ sql_connect(char *host,char *database,char *user,char *password,uint silent)
if (!message && !silent) if (!message && !silent)
{ {
message=1; message=1;
fputs("Waiting",stderr); (void) fflush(stderr); tee_fputs("Waiting",stderr); (void) fflush(stderr);
} }
(void) sleep(5); (void) sleep(5);
if (!silent) if (!silent)
...@@ -1860,75 +2104,82 @@ com_status(String *buffer __attribute__((unused)), ...@@ -1860,75 +2104,82 @@ com_status(String *buffer __attribute__((unused)),
char *line __attribute__((unused))) char *line __attribute__((unused)))
{ {
char *status; char *status;
puts("--------------"); tee_puts("--------------", stdout);
usage(1); /* Print version */ usage(1); /* Print version */
if (connected) if (connected)
{ {
MYSQL_RES *result; MYSQL_RES *result;
LINT_INIT(result); LINT_INIT(result);
printf("\nConnection id:\t\t%ld\n",mysql_thread_id(&mysql)); tee_fprintf(stdout, "\nConnection id:\t\t%ld\n",mysql_thread_id(&mysql));
if (!mysql_query(&mysql,"select DATABASE(),USER()") && if (!mysql_query(&mysql,"select DATABASE(),USER()") &&
(result=mysql_use_result(&mysql))) (result=mysql_use_result(&mysql)))
{ {
MYSQL_ROW cur=mysql_fetch_row(result); MYSQL_ROW cur=mysql_fetch_row(result);
printf("Current database:\t%s\n",cur[0]); tee_fprintf(stdout, "Current database:\t%s\n",cur[0]);
printf("Current user:\t\t%s\n",cur[1]); tee_fprintf(stdout, "Current user:\t\t%s\n",cur[1]);
(void) mysql_fetch_row(result); // Read eof (void) mysql_fetch_row(result); // Read eof
} }
} }
else else
{ {
vidattr(A_BOLD); vidattr(A_BOLD);
printf("\nNo connection\n"); tee_fprintf(stdout, "\nNo connection\n");
vidattr(A_NORMAL); vidattr(A_NORMAL);
return 0; return 0;
} }
if (skip_updates) if (skip_updates)
{ {
vidattr(A_BOLD); vidattr(A_BOLD);
printf("\nAll updates ignored to this database\n"); tee_fprintf(stdout, "\nAll updates ignored to this database\n");
vidattr(A_NORMAL); vidattr(A_NORMAL);
} }
printf("Server version\t\t%s\n", mysql_get_server_info(&mysql)); #ifndef __WIN__
printf("Protocol version\t%d\n", mysql_get_proto_info(&mysql)); tee_fprintf(stdout, "Current pager:\t\t%s\n", pager);
printf("Connection\t\t%s\n", mysql_get_host_info(&mysql)); if (opt_outfile)
printf("Language\t\t%s\n", mysql.charset->name); tee_fprintf(stdout, "Using outfile:\t\tYes: '%s'\n", outfile);
else
printf("Using outfile:\t\tNo\n");
#endif
tee_fprintf(stdout, "Server version:\t\t%s\n", mysql_get_server_info(&mysql));
tee_fprintf(stdout, "Protocol version:\t%d\n", mysql_get_proto_info(&mysql));
tee_fprintf(stdout, "Connection:\t\t%s\n", mysql_get_host_info(&mysql));
tee_fprintf(stdout, "Language:\t\t%s\n", mysql.charset->name);
if (strstr(mysql_get_host_info(&mysql),"TCP/IP") || ! mysql.unix_socket) if (strstr(mysql_get_host_info(&mysql),"TCP/IP") || ! mysql.unix_socket)
printf("TCP port\t\t%d\n", mysql.port); tee_fprintf(stdout, "TCP port:\t\t%d\n", mysql.port);
else else
printf("UNIX socket\t\t%s\n", mysql.unix_socket); tee_fprintf(stdout, "UNIX socket:\t\t%s\n", mysql.unix_socket);
if ((status=mysql_stat(&mysql)) && !mysql_error(&mysql)[0]) if ((status=mysql_stat(&mysql)) && !mysql_error(&mysql)[0])
{ {
char *pos,buff[40]; char *pos,buff[40];
ulong sec; ulong sec;
pos=strchr(status,' '); pos=strchr(status,' ');
*pos++=0; *pos++=0;
printf("%s\t\t\t",status); /* print label */ tee_fprintf(stdout, "%s\t\t\t", status); /* print label */
if ((status=str2int(pos,10,0,LONG_MAX,(long*) &sec))) if ((status=str2int(pos,10,0,LONG_MAX,(long*) &sec)))
{ {
nice_time((double) sec,buff,0); nice_time((double) sec,buff,0);
puts(buff); /* print nice time */ tee_puts(buff, stdout); /* print nice time */
while (*status == ' ') status++; /* to next info */ while (*status == ' ') status++; /* to next info */
} }
if (status) if (status)
{ {
putchar('\n'); tee_fputs("\n", stdout);
puts(status); tee_puts(status, stdout);
} }
} }
if (safe_updates) if (safe_updates)
{ {
vidattr(A_BOLD); vidattr(A_BOLD);
printf("\nNote that we are running in safe_update_mode:\n"); tee_fprintf(stdout, "\nNote that we are running in safe_update_mode:\n");
vidattr(A_NORMAL); vidattr(A_NORMAL);
printf("\ tee_fprintf(stdout, "\
UPDATE and DELETE that doesn't use a key in the WHERE clause are not allowed\n\ UPDATE and DELETE that doesn't use a key in the WHERE clause are not allowed\n\
(One can force UPDATE/DELETE by adding LIMIT # at the end of the command)\n\ (One can force UPDATE/DELETE by adding LIMIT # at the end of the command)\n\
SELECT has an automatic 'LIMIT %lu' if LIMIT is not used\n\ SELECT has an automatic 'LIMIT %lu' if LIMIT is not used\n\
Max number of examined row combination in a join is set to: %lu\n\n", Max number of examined row combination in a join is set to: %lu\n\n",
select_limit,max_join_size); select_limit,max_join_size);
} }
puts("--------------\n"); tee_puts("--------------\n", stdout);
return 0; return 0;
} }
...@@ -1958,7 +2209,7 @@ put_info(const char *str,INFO_TYPE info_type,uint error) ...@@ -1958,7 +2209,7 @@ put_info(const char *str,INFO_TYPE info_type,uint error)
return 1; return 1;
} }
else if (info_type == INFO_RESULT && verbose > 1) else if (info_type == INFO_RESULT && verbose > 1)
puts(str); tee_puts(str, stdout);
if (unbuffered) if (unbuffered)
fflush(stdout); fflush(stdout);
return info_type == INFO_ERROR ? -1 : 0; return info_type == INFO_ERROR ? -1 : 0;
...@@ -1977,13 +2228,13 @@ put_info(const char *str,INFO_TYPE info_type,uint error) ...@@ -1977,13 +2228,13 @@ put_info(const char *str,INFO_TYPE info_type,uint error)
putchar('\007'); /* This should make a bell */ putchar('\007'); /* This should make a bell */
vidattr(A_STANDOUT); vidattr(A_STANDOUT);
if (error) if (error)
(void) fprintf(stderr,"ERROR %d: ",error); (void) tee_fprintf(stderr, "ERROR %d: ", error);
else else
fputs("ERROR: ",stdout); tee_puts("ERROR: ", stdout);
} }
else else
vidattr(A_BOLD); vidattr(A_BOLD);
(void) puts(str); (void) tee_puts(str, stdout);
vidattr(A_NORMAL); vidattr(A_NORMAL);
} }
if (unbuffered) if (unbuffered)
...@@ -1991,6 +2242,7 @@ put_info(const char *str,INFO_TYPE info_type,uint error) ...@@ -1991,6 +2242,7 @@ put_info(const char *str,INFO_TYPE info_type,uint error)
return info_type == INFO_ERROR ? -1 : 0; return info_type == INFO_ERROR ? -1 : 0;
} }
static void remove_cntrl(String &buffer) static void remove_cntrl(String &buffer)
{ {
char *start,*end; char *start,*end;
...@@ -2001,6 +2253,37 @@ static void remove_cntrl(String &buffer) ...@@ -2001,6 +2253,37 @@ static void remove_cntrl(String &buffer)
} }
void tee_fprintf(FILE *file, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
VOID(vfprintf(file, fmt, args));
if (opt_outfile)
VOID(vfprintf(OUTFILE, fmt, args));
va_end(args);
}
void tee_fputs(const char *s, FILE *file)
{
fputs(s, file);
if (opt_outfile)
fputs(s, OUTFILE);
}
void tee_puts(const char *s, FILE *file)
{
fputs(s, file);
fputs("\n", file);
if (opt_outfile)
{
fputs(s, OUTFILE);
fputs("\n", OUTFILE);
}
}
#ifdef __WIN__ #ifdef __WIN__
#include <time.h> #include <time.h>
#else #else
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment