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