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