LP#1485371: Use client-supplied TZ
[OpenSRF.git] / src / srfsh / srfsh.c
1 /**
2         @file srfsh.c
3         @brief Command-line tool for OSRF
4 */
5
6 #include <opensrf/transport_client.h>
7 #include <opensrf/osrf_message.h>
8 #include <opensrf/osrf_app_session.h>
9 #include <time.h>
10 #include <ctype.h>
11 #include <sys/timeb.h>
12 #include <sys/types.h>
13 #include <sys/wait.h>
14
15 #include <opensrf/utils.h>
16 #include <opensrf/log.h>
17
18 #include <signal.h>
19
20 #include <stdio.h>
21 #include <readline/readline.h>
22 #include <readline/history.h>
23
24 /**
25         @brief A struct of convenience for parsing a command line
26 */
27 typedef struct {
28         const char* itr;            /**< iterator for input buffer */
29         growing_buffer* buf;        /**< output buffer */
30 } ArgParser;
31
32 static void get_string_literal( ArgParser* parser );
33 static void get_json_array( ArgParser* parser );
34 static void get_json_object( ArgParser* parser );
35 static void get_misc( ArgParser* parser );
36
37 #define SRFSH_PORT 5222
38 #define COMMAND_BUFSIZE 4096
39
40 static char* tz = NULL;
41
42 /* shell prompt */
43 static const char* prompt = "srfsh# ";
44
45 static char* history_file = NULL;
46
47 //static int child_dead = 0;
48
49 static char* login_session = NULL;
50
51 /* true if we're pretty printing json results */
52 static int pretty_print = 1;
53 /* true if we're bypassing 'less' */
54 static int raw_print = 0;
55
56 /* our jabber connection */
57 static transport_client* client = NULL; 
58
59 /* the last result we received */
60 static osrfMessage* last_result = NULL;
61
62 /* functions */
63 static int process_request( const char* request );
64 static void parse_args( const char* request, osrfStringArray* cmd_array );
65
66 /* handles router requests */
67 static int handle_router( const osrfStringArray* cmd_array );
68
69 /* utility method for print time data */
70 static int handle_time( const osrfStringArray* cmd_array ); 
71
72 /* handles app level requests */
73 static int handle_request( const osrfStringArray* cmd_array, int relay );
74 static int handle_set( const osrfStringArray* cmd_array );
75 static int handle_print( const osrfStringArray* cmd_array );
76 static int send_request( const char* server,
77                                 const char* method, growing_buffer* buffer, int relay );
78 static int parse_error( const char* request );
79 static int router_query_servers( const char* server );
80 static int print_help( void );
81
82 //static int srfsh_client_connect();
83 //static char* tabs(int count);
84 //static void sig_child_handler( int s );
85 //static void sig_int_handler( int s );
86
87 static char* get_request( void );
88 static int load_history( void );
89 static int handle_math( const osrfStringArray* cmd_array );
90 static int do_math( int count, int style );
91 static int handle_introspect( const osrfStringArray* cmd_array );
92 static int handle_login( const osrfStringArray* cmd_array );
93 static int handle_open( const osrfStringArray* cmd_array );
94 static int handle_close( const osrfStringArray* cmd_array );
95 static void close_all_sessions( void );
96
97 static int recv_timeout = 120;
98 static int is_from_script = 0;
99 static int no_bang = 0;
100
101 static osrfHash* server_hash = NULL;
102
103 int main( int argc, char* argv[] ) {
104
105         /* --------------------------------------------- */
106         /* see if they have a .srfsh.xml in their home directory */
107         char* home = getenv("HOME");
108         tz = getenv("TZ");
109         int l = strlen(home) + 36;
110         char fbuf[l];
111         snprintf(fbuf, sizeof(fbuf), "%s/.srfsh.xml", home);
112         
113         if(!access(fbuf, R_OK)) {
114                 if( ! osrf_system_bootstrap_client(fbuf, "srfsh") ) {
115                         fprintf(stderr,"Unable to bootstrap client for requests\n");
116                         osrfLogError( OSRF_LOG_MARK,  "Unable to bootstrap client for requests");
117                         return -1;
118                 }
119
120         } else {
121                 fprintf(stderr,"No Config file found at %s\n", fbuf ); 
122                 return -1;
123         }
124
125         if(argc > 1) {
126                 int f;
127                 int i;
128                 for (i = 1; i < argc; i++) {
129
130                         if( !strcmp( argv[i], "--safe" ) ) {
131                                 no_bang = 1;
132                                 continue;
133                         }
134
135                         /* for now.. the first unrecognized arg is used as a script file for processing */
136                         if (is_from_script) continue;
137
138                         if( (f = open(argv[i], O_RDONLY)) == -1 ) {
139                                 osrfLogError( OSRF_LOG_MARK, "Unable to open file %s for reading, exiting...", argv[i]);
140                                 return -1;
141                         }
142
143                         if(dup2(f, STDIN_FILENO) == -1) {
144                                 osrfLogError( OSRF_LOG_MARK, "Unable to duplicate STDIN, exiting...");
145                                 return -1;
146                         }
147
148                         close(f);
149                         is_from_script = 1;
150                 }
151         }
152
153         // if stdin is not a tty, assume that we're running
154         // a script and don't want to record history
155         if (!isatty(fileno(stdin))) is_from_script = 1;
156
157         /* --------------------------------------------- */
158         if (!is_from_script) load_history();
159
160         client = osrfSystemGetTransportClient();
161         osrfAppSessionSetIngress("srfsh");
162         
163         // Disable special treatment for tabs by readline
164         // (by default they invoke command completion, which
165         // is not useful for srfsh)
166         rl_bind_key( '\t', rl_insert );
167         
168         /* main process loop */
169         int newline_needed = 1;  /* used as boolean */
170         char* request;
171         while( (request = get_request()) ) {
172
173                 // Find first non-whitespace character
174                 
175                 char * cmd = request;
176                 while( isspace( (unsigned char) *cmd ) )
177                         ++cmd;
178
179                 // Remove trailing whitespace.  We know at this point that
180                 // there is at least one non-whitespace character somewhere,
181                 // or we would have already skipped this line.  Hence we
182                 // needn't check to make sure that we don't back up past
183                 // the beginning.
184
185                 {
186                         // The curly braces limit the scope of the end variable
187                         
188                         char * end = cmd + strlen(cmd) - 1;
189                         while( isspace( (unsigned char) *end ) )
190                                 --end;
191                         end[1] = '\0';
192                 }
193
194                 if( !strcasecmp(cmd, "exit") || !strcasecmp(cmd, "quit"))
195                 {
196                         newline_needed = 0;
197                         break; 
198                 }
199
200                 process_request( cmd );
201                 if( !is_from_script && request && *cmd ) {
202                         add_history(request);
203                 }
204
205                 free(request);
206
207                 fflush(stderr);
208                 fflush(stdout);
209         }
210
211         if( newline_needed ) {
212                 
213                 // We left the readline loop after seeing an EOF, not after
214                 // seeing "quit" or "exit".  So we issue a newline in order
215                 // to avoid leaving a dangling prompt.
216
217                 putchar( '\n' );
218         }
219
220         if(history_file != NULL )
221                 write_history(history_file);
222
223         // Free stuff
224         free(request);
225         free(login_session);
226         if( server_hash ) {
227                 if( osrfHashGetCount( server_hash ) > 0 )
228                         close_all_sessions();
229                 osrfHashFree( server_hash );
230         }
231
232         osrf_system_shutdown();
233         return 0;
234 }
235
236 // Get a logical line from one or more calls to readline(),
237 // skipping blank lines and comments.  Stitch continuation
238 // lines together as needed.  Caller is responsible for
239 // freeing the string returned.
240 // If EOF appears before a logical line is completed, 
241 // return NULL.
242 static char* get_request( void ) {
243         char* line;
244         char* p;
245
246         // Get the first physical line of the logical line
247         while( 1 ) {
248                 line = readline( prompt );
249                 if( ! line )
250                         return NULL;     // end of file
251
252                 // Skip leading white space
253                 for( p = line; isspace( *p ); ++p )
254                         ;
255
256                 if( '\\' == *p && '\0' == p[1] ) {
257                         // Just a trailing backslash; skip to next line
258                         free( line );
259                         continue;
260                 } else if( '\0' == p[0] || '#' == *p ) {
261                         free( line );
262                         continue;  // blank line or comment; skip it
263                 } else
264                         break;     // Not blank, not comment; take it
265         }
266
267         char* end = line + strlen( line ) - 1;
268         if( *end != '\\' )
269                 return line;    // No continuation line; we're done
270
271         // Remove the trailing backslash and collect
272         // the continuation line(s) into a growing_buffer
273         *end = '\0';
274
275         growing_buffer* logical_line = buffer_init( 256 );
276         buffer_add( logical_line, p );
277         free( line );
278
279         // Append any continuation lines
280         int finished = 0;      // boolean
281         while( !finished ) {
282                 line = readline( "> " );
283                 if( line ) {
284
285                         // Check for another continuation
286                         end = line + strlen( line ) - 1;
287                         if( '\\' == *end )
288                                 *end = '\0';
289                         else
290                                 finished = 1;
291
292                         buffer_add( logical_line, line );
293                         free( line );
294                 } else {
295                         fprintf( stderr, "Expected continuation line; found end of file\n" );
296                         buffer_free( logical_line );
297                         return NULL;
298                 }
299         }
300
301         return buffer_release( logical_line );
302 }
303
304 static int load_history( void ) {
305
306         char* home = getenv("HOME");
307         int l = strlen(home) + 24;
308         char fbuf[l];
309         snprintf(fbuf, sizeof(fbuf), "%s/.srfsh_history", home);
310         history_file = strdup(fbuf);
311
312         if(!access(history_file, W_OK | R_OK )) {
313                 history_length = 5000;
314                 read_history(history_file);
315         }
316         return 1;
317 }
318
319
320 static int parse_error( const char* request ) {
321         if( ! request )
322                 return 0;
323
324         fprintf( stderr, "???: %s\n", request );
325         return 0;
326
327 }
328
329
330 static int process_request( const char* request ) {
331
332         if( request == NULL )
333                 return 0;
334
335         int ret_val = 0;
336         osrfStringArray* cmd_array = osrfNewStringArray( 32 );
337
338         parse_args( request, cmd_array );
339         int wordcount = cmd_array->size;
340         if( 0 == wordcount ) {
341                 printf( "No words found in command\n" );
342                 osrfStringArrayFree( cmd_array );
343                 return 0;
344         }
345
346         /* pass off to the top level command */
347         const char* command = osrfStringArrayGetString( cmd_array, 0 );
348         if( !strcmp( command, "router" ) )
349                 ret_val = handle_router( cmd_array );
350
351         else if( !strcmp( command, "time" ) )
352                 ret_val = handle_time( cmd_array );
353
354         else if ( !strcmp( command, "request" ) )
355                 ret_val = handle_request( cmd_array, 0 );
356
357         else if ( !strcmp( command, "relay" ) )
358                 ret_val = handle_request( cmd_array, 1 );
359
360         else if ( !strcmp( command, "help" ) )
361                 ret_val = print_help();
362
363         else if ( !strcmp( command, "set" ) )
364                 ret_val = handle_set( cmd_array );
365
366         else if ( !strcmp( command, "print" ) )
367                 ret_val = handle_print( cmd_array );
368
369         else if ( !strcmp( command, "math_bench" ) )
370                 ret_val = handle_math( cmd_array );
371
372         else if ( !strcmp( command, "introspect" ) )
373                 ret_val = handle_introspect( cmd_array );
374
375         else if ( !strcmp( command, "login" ) )
376                 ret_val = handle_login( cmd_array );
377
378         else if ( !strcmp( command, "open" ) )
379                 ret_val = handle_open( cmd_array );
380
381         else if ( !strcmp( command, "close" ) )
382                 ret_val = handle_close( cmd_array );
383
384         else if ( request[0] == '!') {
385                 if (!no_bang) {
386                         system( request + 1 );
387                         ret_val = 1;
388                 }
389         }
390
391         osrfStringArrayFree( cmd_array );
392
393         if(!ret_val)
394                 return parse_error( request );
395         else
396                 return 1;
397 }
398
399
400 static int handle_introspect( const osrfStringArray* cmd_array ) {
401
402         const char* service = osrfStringArrayGetString( cmd_array, 1 );
403         if( ! service )
404                 return 0;
405
406         fprintf(stderr, "--> %s\n", service );
407
408         // Build a command in a suitably-sized
409         // buffer and then parse it
410
411         size_t len;
412         const char* method = osrfStringArrayGetString( cmd_array, 2 );
413         if( method ) {
414                 static const char text[] = "request %s opensrf.system.method %s";
415                 len = sizeof( text ) + strlen( service ) + strlen( method );
416                 char buf[len];
417                 snprintf( buf, sizeof(buf), text, service, method );
418                 return process_request( buf );
419
420         } else {
421                 static const char text[] = "request %s opensrf.system.method.all";
422                 len = sizeof( text ) + strlen( service );
423                 char buf[len];
424                 snprintf( buf, sizeof(buf), text, service );
425                 return process_request( buf );
426
427         }
428 }
429
430
431 static int handle_login( const osrfStringArray* cmd_array ) {
432
433         const char* username = osrfStringArrayGetString( cmd_array, 1 );
434         const char* password = osrfStringArrayGetString( cmd_array, 2 );
435
436         if( username && password ) {
437
438                 const char* type                = osrfStringArrayGetString( cmd_array, 3 );
439                 const char* orgloc              = osrfStringArrayGetString( cmd_array, 4 );
440                 const char* workstation = osrfStringArrayGetString( cmd_array, 5 );
441                 int orgloci = (orgloc) ? atoi(orgloc) : 0;
442                 if(!type) type = "opac";
443
444                 char login_text[] = "request open-ils.auth open-ils.auth.authenticate.init \"%s\"";
445                 size_t len = sizeof( login_text ) + strlen(username) + 1;
446
447                 char buf[len];
448                 snprintf( buf, sizeof(buf), login_text, username );
449                 process_request(buf);
450
451                 const char* hash;
452                 if(last_result && last_result->_result_content) {
453                         jsonObject* r = last_result->_result_content;
454                         hash = jsonObjectGetString(r);
455                 } else return 0;
456
457                 char* pass_buf = md5sum(password);
458
459                 size_t both_len = strlen( hash ) + strlen( pass_buf ) + 1;
460                 char both_buf[both_len];
461                 snprintf(both_buf, sizeof(both_buf), "%s%s", hash, pass_buf);
462
463                 char* mess_buf = md5sum(both_buf);
464
465                 growing_buffer* argbuf = buffer_init(64);
466                 buffer_fadd(argbuf, 
467                                 "request open-ils.auth open-ils.auth.authenticate.complete "
468                                 "{ \"username\" : \"%s\", \"password\" : \"%s\"", username, mess_buf );
469
470                 if(type) buffer_fadd( argbuf, ", \"type\" : \"%s\"", type );
471                 if(orgloci) buffer_fadd( argbuf, ", \"org\" : %d", orgloci );
472                 if(workstation) buffer_fadd( argbuf, ", \"workstation\" : \"%s\"", workstation);
473                 buffer_add_char( argbuf, '}' );
474
475                 free(pass_buf);
476                 free(mess_buf);
477
478                 process_request( argbuf->buf );
479                 buffer_free(argbuf);
480
481                 if( login_session != NULL )
482                         free( login_session );
483
484                 const jsonObject* x = last_result->_result_content;
485                 double authtime = 0;
486                 if(x) {
487                         const char* authtoken = jsonObjectGetString(
488                                         jsonObjectGetKeyConst(jsonObjectGetKeyConst(x,"payload"), "authtoken"));
489                         authtime  = jsonObjectGetNumber(
490                                         jsonObjectGetKeyConst(jsonObjectGetKeyConst(x,"payload"), "authtime"));
491
492                         if(authtoken)
493                                 login_session = strdup(authtoken);
494                         else
495                                 login_session = NULL;
496                 }
497                 else login_session = NULL;
498
499                 printf("Login Session: %s.  Session timeout: %f\n",
500                            (login_session ? login_session : "(none)"), authtime );
501                 
502                 return 1;
503
504         }
505
506         return 0;
507 }
508
509 /**
510         @brief Open connections to one or more specified services.
511         @param cmd_array Pointer to a list of command line chunks.
512         @return 1 in all cases.
513
514         The first chunk of the command line is the "open" command.  Subsequent chunks, if any,
515         are server names.
516
517         Try to open all specified servers.  If no servers are specified, report what servers are
518         currently open.
519 */
520 static int handle_open( const osrfStringArray* cmd_array ) {
521         if( NULL == osrfStringArrayGetString( cmd_array, 1 ) ) {
522                 if( ! server_hash || osrfHashGetCount( server_hash ) == 0 ) {
523                         printf( "No services are currently open\n" );
524                         return 1;
525                 }
526
527                 printf( "Service(s) currently open:\n" );
528
529                 osrfHashIterator* itr = osrfNewHashIterator( server_hash );
530                 while( osrfHashIteratorNext( itr ) ) {
531                         printf( "\t%s\n", osrfHashIteratorKey( itr ) );
532                 }
533                 osrfHashIteratorFree( itr );
534                 return 1;
535         }
536
537         if( ! server_hash )
538                 server_hash = osrfNewHash( 6 );
539
540         int i;
541         for( i = 1; ; ++i ) {    // for each requested service
542                 const char* server = osrfStringArrayGetString( cmd_array, i );
543                 if( ! server )
544                         break;
545
546                 if( osrfHashGet( server_hash, server ) ) {
547                         printf( "Service %s is already open\n", server );
548                         continue;
549                 }
550
551                 // Try to open a session with the current specified server
552                 osrfAppSession* session = osrfAppSessionClientInit(server);
553
554                 if(!osrfAppSessionConnect(session)) {
555                         fprintf(stderr, "Unable to open service %s\n", server);
556                         osrfLogWarning( OSRF_LOG_MARK, "Unable to open remote service %s\n", server );
557                         osrfAppSessionFree( session );
558                 } else {
559                         osrfHashSet( server_hash, session, server );
560                         printf( "Service %s opened\n", server );
561                 }
562         }
563
564         return 1;
565 }
566
567 /**
568         @brief Close connections to one or more specified services.
569         @param cmd_array Pointer to a list of command line chunks.
570         @return 1 if any services were closed, or 0 if there were none to close.
571
572         The first chunk of the command line is the "close" command.  Subsequent chunks, if any,
573         are server names.
574 */
575 static int handle_close( const osrfStringArray* cmd_array ) {
576         if( cmd_array->size < 2 ) {
577                 fprintf( stderr, "No service specified for close\n" );
578                 return 0;
579         }
580
581         int i;
582         for( i = 1; ; ++i ) {
583                 const char* server = osrfStringArrayGetString( cmd_array, i );
584                 if( ! server )
585                         break;
586
587                 osrfAppSession* session = osrfHashRemove( server_hash, server );
588                 if( ! session ) {
589                         printf( "Service \"%s\" is not open\n", server );
590                         continue;
591                 }
592
593                 osrf_app_session_disconnect( session );
594                 osrfAppSessionFree( session );
595                 printf( "Service \"%s\" closed\n", server );
596         }
597
598         return 1;
599 }
600
601 /**
602         @brief Close all currently open connections to services.
603  */
604 static void close_all_sessions( void ) {
605
606         osrfAppSession* session;
607         osrfHashIterator* itr = osrfNewHashIterator( server_hash );
608
609         while(( session = osrfHashIteratorNext( itr ) )) {
610                 osrf_app_session_disconnect( session );
611                 osrfAppSessionFree( session );
612         }
613
614         osrfHashIteratorFree( itr );
615 }
616
617 static int handle_set( const osrfStringArray* cmd_array ) {
618
619         const char* variable = osrfStringArrayGetString( cmd_array, 1 );
620         if( variable ) {
621
622                 const char* val = osrfStringArrayGetString( cmd_array, 2 );
623                 if( val ) {
624
625                         if(!strcmp(variable,"pretty_print")) {
626                                 if(!strcmp(val,"true")) {
627                                         pretty_print = 1;
628                                         printf("pretty_print = true\n");
629                                         return 1;
630                                 } else if(!strcmp(val,"false")) {
631                                         pretty_print = 0;
632                                         printf("pretty_print = false\n");
633                                         return 1;
634                                 }
635                         }
636
637                         if(!strcmp(variable,"raw_print")) {
638                                 if(!strcmp(val,"true")) {
639                                         raw_print = 1;
640                                         printf("raw_print = true\n");
641                                         return 1;
642                                 } else if(!strcmp(val,"false")) {
643                                         raw_print = 0;
644                                         printf("raw_print = false\n");
645                                         return 1;
646                                 }
647                         }
648
649                 }
650         }
651
652         return 0;
653 }
654
655
656 static int handle_print( const osrfStringArray* cmd_array ) {
657
658         const char* variable = osrfStringArrayGetString( cmd_array, 1 );
659
660         if( variable ) {
661                 if(!strcmp(variable,"pretty_print")) {
662                         if(pretty_print) {
663                                 printf("pretty_print = true\n");
664                                 return 1;
665                         } else {
666                                 printf("pretty_print = false\n");
667                                 return 1;
668                         }
669                 }
670
671                 if(!strcmp(variable,"raw_print")) {
672                         if(raw_print) {
673                                 printf("raw_print = true\n");
674                                 return 1;
675                         } else {
676                                 printf("raw_print = false\n");
677                                 return 1;
678                         }
679                 }
680
681                 if(!strcmp(variable,"login")) {
682                         printf("login session = %s\n",
683                                    login_session ? login_session : "(none)" );
684                         return 1;
685                 }
686
687         }
688         return 0;
689 }
690
691 static int handle_router( const osrfStringArray* cmd_array ) {
692
693         if(!client)
694                 return 1;
695
696         int i;
697
698         const char* word_1 = osrfStringArrayGetString( cmd_array, 1 );
699         const char* word_2 = osrfStringArrayGetString( cmd_array, 2 );
700         if( word_1 ) {
701                 if( !strcmp( word_1,"query") ) {
702
703                         if( word_2 && !strcmp( word_2, "servers" ) ) {
704                                 for( i=3; i < COMMAND_BUFSIZE - 3; i++ ) {
705                                         const char* word = osrfStringArrayGetString( cmd_array, i );
706                                         if( word )
707                                                 router_query_servers( word );
708                                         else
709                                                 break;
710                                 }
711                                 return 1;
712                         }
713                         return 0;
714                 }
715                 return 0;
716         }
717         return 0;
718 }
719
720
721
722 static int handle_request( const osrfStringArray* cmd_array, int relay ) {
723
724         if(!client)
725                 return 1;
726
727         const char* server = osrfStringArrayGetString( cmd_array, 1 );
728         const char* method = osrfStringArrayGetString( cmd_array, 2 );
729
730         if( server ) {
731                 int i;
732                 growing_buffer* buffer = NULL;
733                 if( !relay ) {
734                         int first = 1;   // boolean
735                         buffer = buffer_init( 128 );
736                         buffer_add_char( buffer, '[' );
737                         for(i = 3; ; i++ ) {
738                                 const char* word = osrfStringArrayGetString( cmd_array, i );
739                                 if( !word )
740                                         break;
741
742                                 if( first )
743                                         first = 0;
744                                 else
745                                         buffer_add( buffer, ", " );
746
747                                 buffer_add( buffer, word );
748
749                                 /* remove trailing semicolon if user accidentally entered it */
750                                 if( word[ strlen( word ) - 1 ] == ';' )
751                                         buffer_chomp( buffer );
752                         }
753                         buffer_add_char( buffer, ']' );
754                 }
755
756                 int rc = send_request( server, method, buffer, relay );
757                 buffer_free( buffer );
758                 return rc;
759         } 
760
761         return 0;
762 }
763
764 int send_request( const char* server,
765                 const char* method, growing_buffer* buffer, int relay ) {
766         if( server == NULL || method == NULL )
767                 return 0;
768
769         jsonObject* params = NULL;
770         if( !relay ) {
771                 if( buffer != NULL && buffer->n_used > 0 ) {
772                         // Temporarily redirect parsing error messages to stderr
773                         osrfLogToStderr();
774                         params = jsonParse( OSRF_BUFFER_C_STR( buffer ) );
775                         osrfRestoreLogType();
776                 }
777         } else {
778                 if(!last_result || ! last_result->_result_content) { 
779                         printf("We're not going to call 'relay' with no result params\n");
780                         return 1;
781                 }
782                 else {
783                         params = jsonNewObject(NULL);
784                         jsonObjectPush(params, last_result->_result_content );
785                 }
786         }
787
788         if(buffer->n_used > 0 && params == NULL) {
789                 fprintf(stderr, "JSON error detected, not executing\n");
790                 jsonObjectFree(params);
791                 return 1;
792         }
793
794         int session_is_temporary;    // boolean
795         osrfAppSession* session = osrfHashGet( server_hash, server );
796         if( session ) {
797                 session_is_temporary = 0;     // use an existing session
798         } else {
799                 session = osrfAppSessionClientInit(server);   // open a session
800                 session_is_temporary = 1;                     // just for this request
801         }
802
803         if (tz) osrf_app_session_set_tz(session,tz);
804
805         double start = get_timestamp_millis();
806
807         int req_id = osrfAppSessionSendRequest( session, params, method, 1 );
808         if( -1 == req_id ) {
809                 fprintf(stderr, "Unable to communicate with service %s\n", server);
810                 osrfLogWarning( OSRF_LOG_MARK,
811                                 "Unable to communicate with remote service %s\n", server );
812                 osrfAppSessionFree( session );
813                 jsonObjectFree(params);
814                 return 1;
815         }
816         jsonObjectFree(params);
817
818         osrfMessage* omsg = osrfAppSessionRequestRecv( session, req_id, recv_timeout );
819
820         if(!omsg) 
821                 printf("\nReceived no data from server\n");
822
823         signal(SIGPIPE, SIG_IGN);
824
825         FILE* less; 
826         if(!is_from_script) less = popen( "less -EX", "w");
827         else less = stdout;
828
829         if( less == NULL ) { less = stdout; }
830
831         growing_buffer* resp_buffer = buffer_init(4096);
832
833         while(omsg) {
834
835                 if(raw_print) {
836
837                         if(omsg->_result_content) {
838         
839                                 osrfMessageFree(last_result);
840                                 last_result = omsg;
841         
842                                 char* content;
843         
844                                 if( pretty_print ) {
845                                         char* j = jsonObjectToJSON(omsg->_result_content);
846                                         if( j ) {
847                                                 content = jsonFormatString(j);
848                                                 free(j);
849                                         } else
850                                                 content = strdup( "(null)" );
851                                 } else {
852                                         content = jsonObjectToJSON(omsg->_result_content);
853                                         if( ! content )
854                                                 content = strdup( "(null)" );
855                                 }
856
857                                 printf( "\nReceived Data: %s\n", content );
858                                 free(content);
859         
860                         } else {
861
862                                 char code[16];
863                                 snprintf( code, sizeof(code), "%d", omsg->status_code );
864                                 buffer_add( resp_buffer, code );
865
866                                 printf( "\nReceived Exception:\nName: %s\nStatus: %s\nStatus: %s\n", 
867                                                 omsg->status_name, omsg->status_text, code );
868
869                                 fflush(stdout);
870                                 osrfMessageFree(omsg);
871                         }
872
873                 } else {
874
875                         if(omsg->_result_content) {
876         
877                                 osrfMessageFree(last_result);
878                                 last_result = omsg;
879         
880                                 char* content;
881         
882                                 if( pretty_print && omsg->_result_content ) {
883                                         char* j = jsonObjectToJSON(omsg->_result_content);
884                                         if( j ) {
885                                                 content = jsonFormatString(j);
886                                                 free(j);
887                                         } else
888                                                 content = strdup( "(null)" );
889                                 } else {
890                                         content = jsonObjectToJSON(omsg->_result_content);
891                                         if( ! content )
892                                                 content = strdup( "(null)" );
893                                 }
894
895                                 buffer_add( resp_buffer, "\nReceived Data: " );
896                                 buffer_add( resp_buffer, content );
897                                 buffer_add_char( resp_buffer, '\n' );
898                                 free(content);
899
900                         } else {
901
902                                 buffer_add( resp_buffer, "\nReceived Exception:\nName: " );
903                                 buffer_add( resp_buffer, omsg->status_name );
904                                 buffer_add( resp_buffer, "\nStatus: " );
905                                 buffer_add( resp_buffer, omsg->status_text );
906                                 buffer_add( resp_buffer, "\nStatus: " );
907                                 char code[16];
908                                 snprintf( code, sizeof(code), "%d", omsg->status_code );
909                                 buffer_add( resp_buffer, code );
910                                 osrfMessageFree(omsg);
911                         }
912                 }
913
914                 omsg = osrfAppSessionRequestRecv( session, req_id, recv_timeout );
915
916         }
917
918         double end = get_timestamp_millis();
919
920         fputs( resp_buffer->buf, less );
921         buffer_free( resp_buffer );
922         fputs("\n------------------------------------\n", less);
923         if( osrf_app_session_request_complete( session, req_id ))
924                 fputs("Request Completed Successfully\n", less);
925
926         fprintf(less, "Request Time in seconds: %.6f\n", end - start );
927         fputs("------------------------------------\n", less);
928
929         if(!is_from_script) pclose(less);
930
931         osrf_app_session_request_finish( session, req_id );
932
933         if( session_is_temporary )
934                 osrfAppSessionFree( session );
935
936         return 1;
937
938
939 }
940
941 static int handle_time( const osrfStringArray* cmd_array ) {
942
943         const char* word_1 = osrfStringArrayGetString( cmd_array, 1 );
944         if( !word_1 ) {
945                 printf("%f\n", get_timestamp_millis());
946         } else {
947                 time_t epoch = (time_t) atoi( word_1 );
948                 printf("%s", ctime(&epoch));
949         }
950         return 1;
951 }
952
953
954 static int router_query_servers( const char* router_server ) {
955
956         if( ! router_server || strlen(router_server) == 0 ) 
957                 return 0;
958
959         const static char router_text[] = "router@%s/router";
960         size_t len = sizeof( router_text ) + strlen( router_server ) + 1;
961         char rbuf[len];
962         snprintf(rbuf, sizeof(rbuf), router_text, router_server );
963                 
964         transport_message* send = 
965                 message_init( "servers", NULL, NULL, rbuf, NULL );
966         message_set_router_info( send, NULL, NULL, NULL, "query", 0 );
967
968         client_send_message( client, send );
969         message_free( send );
970
971         transport_message* recv = client_recv( client, -1 );
972         if( recv == NULL ) {
973                 fprintf(stderr, "NULL message received from router\n");
974                 return 1;
975         }
976         
977         printf( 
978                         "---------------------------------------------------------------------------------\n"
979                         "Received from 'server' query on %s\n"
980                         "---------------------------------------------------------------------------------\n"
981                         "original reg time | latest reg time | last used time | class | server\n"
982                         "---------------------------------------------------------------------------------\n"
983                         "%s"
984                         "---------------------------------------------------------------------------------\n"
985                         , router_server, recv->body );
986
987         message_free( recv );
988         
989         return 1;
990 }
991
992 static int print_help( void ) {
993
994         fputs(
995                         "---------------------------------------------------------------------------------\n"
996                         "General commands:\n"
997                         "---------------------------------------------------------------------------------\n"
998                         "help                   - Display this message\n",
999                         stdout );
1000         if (!no_bang) fputs(
1001                         "!<command> [args]      - Forks and runs the given command in the shell\n",
1002                         stdout );
1003         fputs(
1004                 /*
1005                         "time                   - Prints the current time\n"
1006                         "time <timestamp>       - Formats seconds since epoch into readable format\n"
1007                 */
1008                         "set <variable> <value> - Set a srfsh variable (e.g. set pretty_print true )\n"
1009                         "print <variable>       - Displays the value of a srfsh variable\n"
1010                         "\n"
1011                         "---------------------------------------------------------------------------------\n"
1012                         "Variables:\n"
1013                         "---------------------------------------------------------------------------------\n"
1014                         "pretty_print            - Display nicely formatted JSON results\n"
1015                         "       - Accepted values: true, false\n"
1016                         "       - Default value: true\n"
1017                         "\n"
1018                         "raw_print               - Pass JSON results through 'less' paging command\n"
1019                         "       - Accepted values: true, false\n"
1020                         "       - Default value: false\n"
1021                         "\n"
1022                         "---------------------------------------------------------------------------------\n"
1023                         "Commands for OpenSRF services and methods:\n"
1024                         "---------------------------------------------------------------------------------\n"
1025                         "introspect <service> [\"method-name\"]\n"
1026                         "       - Prints service API, limited to the methods that match the optional\n"
1027                         "                right-truncated method-name parameter\n"
1028                         "\n"
1029                         "request <service> <method> [ <JSON formatted string of params> ]\n"
1030                         "       - Anything passed in will be wrapped in a JSON array,\n"
1031                         "               so add commas if there is more than one param\n"
1032                         "\n"
1033                         "router query servers <server1 [, server2, ...]>\n"
1034                         "       - Returns stats on connected services\n"
1035                         "\n"
1036                         "relay <service> <method>\n"
1037                         "       - Performs the requested query using the last received result as the param\n"
1038                         "\n"
1039                         "math_bench <num_batches> [0|1|2]\n"
1040                         "       - 0 means don't reconnect, 1 means reconnect after each batch of 4, and\n"
1041                         "                2 means reconnect after every request\n"
1042                         "\n"
1043                         "---------------------------------------------------------------------------------\n"
1044                         " Commands for Evergreen\n"
1045                         "---------------------------------------------------------------------------------\n"
1046                         "login <username> <password> [type] [org_unit] [workstation]\n"
1047                         "       - Logs into the 'server' and displays the session id\n"
1048                         "       - To view the session id later, enter: print login\n"
1049                         "---------------------------------------------------------------------------------\n"
1050                         "\n"
1051                         "Note: long output is piped through 'less' unless the 'raw_print' variable\n"
1052                         "is true.  To search in 'less', type: /<search>\n"
1053                         "---------------------------------------------------------------------------------\n"
1054                         "\n",
1055                         stdout );
1056
1057         return 1;
1058 }
1059
1060
1061 /*
1062 static char* tabs(int count) {
1063         growing_buffer* buf = buffer_init(24);
1064         int i;
1065         for(i=0;i!=count;i++)
1066                 buffer_add(buf, "  ");
1067
1068         char* final = buffer_data( buf );
1069         buffer_free( buf );
1070         return final;
1071 }
1072 */
1073
1074 /**
1075         @brief Execute the "math_bench" command.
1076         @param cmd_array A list of command arguments.
1077         @return 1 if successful, 0 if not.
1078
1079         The first command argument is required.  It is the number of iterations requested.  If
1080         it is less than 1, it is coerced to 1.
1081
1082         The second command argument is optional, with allowed values of 0 (the default), 1, or 2.
1083         It controls when and whether we call osrf_app_session_disconnect().  If this argument is
1084         out of range, it is coerced to a value of 0 or 2.
1085 */
1086 static int handle_math( const osrfStringArray* cmd_array ) {
1087         const char* word = osrfStringArrayGetString( cmd_array, 1 );
1088         if( word ) {
1089                 int count = atoi( word );
1090                 if( count < 1 )
1091                         count = 1;
1092
1093                 int style = 0;
1094                 const char* style_arg = osrfStringArrayGetString( cmd_array, 2 );
1095                 if( style_arg ) {
1096                         style = atoi( style_arg );
1097                         if( style > 2 )
1098                                 style = 2;
1099                         else if( style < 0 )
1100                                 style = 0;
1101                 }
1102
1103                 return do_math( count, style );
1104         }
1105         return 0;
1106 }
1107
1108
1109 static int do_math( int count, int style ) {
1110
1111         osrfAppSession* session = osrfAppSessionClientInit( "opensrf.math" );
1112         osrfAppSessionConnect(session);
1113
1114         jsonObject* params = jsonNewObjectType( JSON_ARRAY );
1115         jsonObjectPush(params,jsonNewObject("1"));
1116         jsonObjectPush(params,jsonNewObject("2"));
1117
1118         char* methods[] = { "add", "sub", "mult", "div" };
1119         char* answers[] = { "3", "-1", "2", "0.5" };
1120
1121         // Initialize timings to zero.  This shouldn't make a difference, because
1122         // we overwrite each timing anyway before reporting them.
1123         float times[ count * 4 ];
1124         int fi;
1125         for( fi = 0; fi < count; ++fi )
1126                 times[ fi ] = 0.0;
1127
1128         int k;
1129         for(k=0;k!=100;k++) {
1130                 if(!(k%10)) 
1131                         fprintf(stderr,"|");
1132                 else
1133                         fprintf(stderr,".");
1134         }
1135
1136         fprintf(stderr,"\n\n");
1137
1138         int running = 0;
1139         int i;
1140         for(i=0; i!= count; i++) {
1141
1142                 int j;
1143                 for(j=0; j != 4; j++) {
1144
1145                         ++running;
1146
1147                         double start = get_timestamp_millis();
1148                         int req_id = osrfAppSessionSendRequest( session, params, methods[j], 1 );
1149                         osrfMessage* omsg = osrfAppSessionRequestRecv( session, req_id, 5 );
1150                         double end = get_timestamp_millis();
1151
1152                         times[(4*i) + j] = end - start;
1153
1154                         if(omsg) {
1155         
1156                                 if(omsg->_result_content) {
1157                                         char* jsn = jsonObjectToJSON(omsg->_result_content);
1158                                         if(!strcmp(jsn, answers[j]))
1159                                                 fprintf(stderr, "+");
1160                                         else
1161                                                 fprintf(stderr, "\n![%s] - should be %s\n", jsn, answers[j] );
1162                                         free(jsn);
1163                                 }
1164
1165
1166                                 osrfMessageFree(omsg);
1167                 
1168                         } else { fprintf( stderr, "\nempty message for tt: %d\n", req_id ); }
1169
1170                         osrf_app_session_request_finish( session, req_id );
1171
1172                         if(style == 2)
1173                                 osrf_app_session_disconnect( session );
1174
1175                         if(!(running%100))
1176                                 fprintf(stderr,"\n");
1177                 }
1178
1179                 if(style==1)
1180                         osrf_app_session_disconnect( session );
1181         }
1182
1183         osrfAppSessionFree( session );
1184         jsonObjectFree(params);
1185
1186         int c;
1187         float total = 0;
1188         for(c=0; c!= count*4; c++) 
1189                 total += times[c];
1190
1191         float avg = total / (count*4); 
1192         fprintf(stderr, "\n      Average round trip time: %f\n", avg );
1193
1194         return 1;
1195 }
1196
1197 /**
1198         @name Command line parser
1199
1200         This group of functions parses the command line into a series of chunks, and stores
1201         the chunks in an osrfStringArray.
1202
1203         A chunk may consist of a JSON string, complete with square brackets, curly braces, and
1204         embedded white space.  It wouldn't work simply to break up the line into  tokens
1205         separated by white space.  Sometimes white space separates chunks, and sometimes it
1206         occurs within a chunk.
1207
1208         When it sees a left square bracket or curly brace, the parser goes into JSON mode,
1209         collecting characters up to the corresponding right square bracket or curly brace.
1210         It also eliminates most kinds of unnecessary white space.
1211
1212         The JSON parsing is rudimentary.  It does not validate the syntax -- it merely looks
1213         for the end of the JSON string.  Eventually the JSON string will be passed to a real
1214         JSON parser, which will detect and report syntax errors.
1215
1216         When not in JSON mode, the parser collects tokens separated by white space.  It also
1217         collects character strings in quotation marks, possibly including embedded white space.
1218         Within a quoted string, an embedded quotation mark does not terminate the string if it
1219         is escaped by a preceding backslash.
1220 */
1221
1222 /**
1223         @brief Collect a string literal enclosed by quotation marks.
1224         @param parser Pointer to an ArgParser
1225
1226         A quotation mark serves as a terminator unless it is escaped by a preceding backslash.
1227         In the latter case, we collect both the backslash and the escaped quotation mark.
1228 */
1229 static void get_string_literal( ArgParser* parser ) {
1230         // Collect character until the first unescaped quotation mark, or EOL
1231         do {
1232                 OSRF_BUFFER_ADD_CHAR( parser->buf, *parser->itr );
1233
1234                 // Don't stop at a quotation mark if it's escaped
1235                 if( '\\' == *parser->itr && '\"' == *( parser->itr + 1 ) ) {
1236                                 OSRF_BUFFER_ADD_CHAR( parser->buf, '\"' );
1237                                 ++parser->itr;
1238                 }
1239
1240                 ++parser->itr;
1241         } while( *parser->itr && *parser->itr != '\"' );
1242
1243         OSRF_BUFFER_ADD_CHAR( parser->buf, '\"' );
1244         ++parser->itr;
1245 }
1246
1247 /**
1248         @brief Collect a JSON array (enclosed by square brackets).
1249         @param parser Pointer to an ArgParser.
1250
1251         Collect characters until you find the closing square bracket.  Collect any intervening
1252         JSON arrays, JSON objects, or string literals recursively.
1253  */
1254 static void get_json_array( ArgParser* parser ) {
1255
1256         OSRF_BUFFER_ADD_CHAR( parser->buf, '[' );
1257         ++parser->itr;
1258
1259         // Collect characters through the closing square bracket
1260         while( *parser->itr != ']' ) {
1261
1262                 if( '\"' == *parser->itr ) {
1263                         get_string_literal( parser );
1264                 } else if( '[' == *parser->itr ) {
1265                         get_json_array( parser );
1266                 } else if( '{' == *parser->itr ) {
1267                         get_json_object( parser );
1268                 } else if( isspace( (unsigned char) *parser->itr ) ) {
1269                         ++parser->itr;   // Ignore white space
1270                 } else if ( '\0' == *parser->itr ) {
1271                         return;   // Ignore failure to close the object
1272                 } else {
1273                         get_misc( parser );
1274                         // make sure that bare words don't run together
1275                         OSRF_BUFFER_ADD_CHAR( parser->buf, ' ' );
1276                 }
1277         } // end while
1278
1279         OSRF_BUFFER_ADD_CHAR( parser->buf, ']' );
1280         ++parser->itr;
1281 }
1282
1283 /**
1284         @brief Collect a JSON object (enclosed by curly braces).
1285         @param parser Pointer to an ArgParser.
1286
1287         Collect characters until you find the closing curly brace.  Collect any intervening
1288         JSON arrays, JSON objects, or string literals recursively.
1289  */
1290 static void get_json_object( ArgParser* parser ) {
1291
1292         OSRF_BUFFER_ADD_CHAR( parser->buf, '{' );
1293         ++parser->itr;
1294
1295         // Collect characters through the closing curly brace
1296         while( *parser->itr != '}' ) {
1297
1298                 if( '\"' == *parser->itr ) {
1299                         get_string_literal( parser );
1300                 } else if( '[' == *parser->itr ) {
1301                         get_json_array( parser );
1302                 } else if( '{' == *parser->itr ) {
1303                         get_json_object( parser );
1304                 } else if( isspace( (unsigned char) *parser->itr ) ) {
1305                         ++parser->itr;   // Ignore white space
1306                 } else if ( '\0' == *parser->itr ) {
1307                         return;   // Ignore failure to close the object
1308                 } else {
1309                         get_misc( parser );
1310                         // make sure that bare words don't run together
1311                         OSRF_BUFFER_ADD_CHAR( parser->buf, ' ' );
1312                 }
1313         } // end while
1314
1315         OSRF_BUFFER_ADD_CHAR( parser->buf, '}' );
1316         ++parser->itr;
1317 }
1318
1319 /**
1320         @brief Collect a token terminated by white space or a ']' or '}' character.
1321         @param parser Pointer to an ArgParser
1322
1323         For valid JSON, the chunk collected here would be either a number or one of the
1324         JSON key words "null", "true", or "false".  However at this stage we're not finicky.
1325         We just collect whatever we see until we find a terminator.
1326 */
1327 static void get_misc( ArgParser* parser ) {
1328         // Collect characters until we see one that doesn't belong
1329         while( 1 ) {
1330                 OSRF_BUFFER_ADD_CHAR( parser->buf, *parser->itr );
1331                 ++parser->itr;
1332                 char c = *parser->itr;
1333                 if( '\0' == c || isspace( (unsigned char) c ) 
1334                         || '{' == c || '}' == c || '[' == c || ']' == c || '\"' == c ) {
1335                         break;
1336                 }
1337         }
1338 }
1339
1340 /**
1341         @brief Parse the command line.
1342         @param request Pointer to the command line
1343         @param cmd_array Pointer to an osrfStringArray to hold the output of the parser.
1344
1345         The parser operates by recursive descent.  We build each chunk of command line in a
1346         growing_buffer belonging to an ArgParser, and then load the chunk into a slot in an
1347         osrfStringArray supplied by the calling code.
1348 */
1349 static void parse_args( const char* request, osrfStringArray* cmd_array )
1350 {
1351         ArgParser parser;
1352
1353         // Initialize the ArgParser
1354         parser.itr = request;
1355         parser.buf = buffer_init( 128 );
1356
1357         int done = 0;               // boolean
1358         while( !done ) {
1359                 OSRF_BUFFER_RESET( parser.buf );
1360
1361                 // skip any white space or commas
1362                 while( *parser.itr
1363                            && ( isspace( (unsigned char) *parser.itr ) || ',' == *parser.itr ) )
1364                         ++parser.itr;
1365
1366                 if( '\0' == *parser.itr )
1367                         done = 1;
1368                 else if( '{' == *parser.itr ) {
1369                         // Load a JSON object
1370                         get_json_object( &parser );
1371                 } else if( '[' == *parser.itr ) {
1372                         // Load a JSON array
1373                         get_json_array( &parser );
1374                 } else if( '\"' == *parser.itr ) {
1375                         // Load a string literal
1376                         get_string_literal( &parser );
1377                 } else {
1378                         // Anything else is delimited by white space
1379                         do {
1380                                 OSRF_BUFFER_ADD_CHAR( parser.buf, *parser.itr );
1381                                 ++parser.itr;
1382                         } while( *parser.itr && ! isspace( (unsigned char) *parser.itr ) );
1383                 }
1384
1385                 // Remove a trailing comma, if present
1386                 char lastc = OSRF_BUFFER_C_STR( parser.buf )[
1387                         strlen( OSRF_BUFFER_C_STR( parser.buf )  ) - 1 ];
1388                 if( ',' == lastc )
1389                         buffer_chomp( parser.buf );
1390
1391                 // Add the chunk to the osrfStringArray
1392                 const char* s = OSRF_BUFFER_C_STR( parser.buf );
1393                 if( s && *s ) {
1394                         osrfStringArrayAdd( cmd_array, s );
1395                 }
1396         }
1397
1398         buffer_free( parser.buf );
1399 }
1400 /*@}*/
1401