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