dc1c73391e119f1e5c82dda44208719a9915b92b
[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* server = osrfStringArrayGetString( cmd_array, 1 );
707         const char* method = osrfStringArrayGetString( cmd_array, 2 );
708
709         if( server ) {
710                 int i;
711                 growing_buffer* buffer = NULL;
712                 if( !relay ) {
713                         int first = 1;   // boolean
714                         buffer = buffer_init( 128 );
715                         buffer_add( buffer, "[" );
716                         for(i = 3; ; i++ ) {
717                                 const char* word = osrfStringArrayGetString( cmd_array, i );
718                                 if( !word )
719                                         break;
720
721                                 if( first )
722                                         first = 0;
723                                 else
724                                         buffer_add( buffer, ", " );
725
726                                 buffer_add( buffer, word );
727
728                                 /* remove trailing semicolon if user accidentally entered it */
729                                 if( word[ strlen( word ) - 1 ] == ';' )
730                                         buffer_chomp( buffer );
731                         }
732                         buffer_add_char( buffer, ']' );
733                 }
734
735                 int rc = send_request( server, method, buffer, relay );
736                 buffer_free( buffer );
737                 return rc;
738         } 
739
740         return 0;
741 }
742
743 int send_request( const char* server,
744                 const char* method, growing_buffer* buffer, int relay ) {
745         if( server == NULL || method == NULL )
746                 return 0;
747
748         jsonObject* params = NULL;
749         if( !relay ) {
750                 if( buffer != NULL && buffer->n_used > 0 ) {
751                         // Temporarily redirect parsing error messages to stderr
752                         osrfLogToStderr();
753                         params = jsonParse( OSRF_BUFFER_C_STR( buffer ) );
754                         osrfRestoreLogType();
755                         if( params == NULL) {
756                                 fprintf(stderr, "JSON error detected, not executing\n");
757                                 return 1;
758                         }
759                 }
760         } else {
761                 if(!last_result || ! last_result->_result_content) { 
762                         printf("We're not going to call 'relay' with no result params\n");
763                         return 1;
764                 }
765                 else {
766                         params = jsonNewObject(NULL);
767                         jsonObjectPush(params, last_result->_result_content );
768                 }
769         }
770
771         int session_is_temporary;    // boolean
772         osrfAppSession* session = osrfHashGet( server_hash, server );
773         if( session ) {
774                 session_is_temporary = 0;     // use an existing session
775         } else {
776                 session = osrfAppSessionClientInit(server);   // open a session
777                 session_is_temporary = 1;                     // just for this request
778         }
779
780         if(!osrfAppSessionConnect(session)) {
781                 fprintf(stderr, "Unable to communicate with service %s\n", server);
782                 osrfLogWarning( OSRF_LOG_MARK,  "Unable to connect to remote service %s\n", server );
783                 osrfAppSessionFree( session );
784                 jsonObjectFree(params);
785                 return 1;
786         }
787
788         double start = get_timestamp_millis();
789
790         int req_id = osrfAppSessionSendRequest( session, params, method, 1 );
791         jsonObjectFree(params);
792
793         osrfMessage* omsg = osrfAppSessionRequestRecv( session, req_id, recv_timeout );
794
795         if(!omsg) 
796                 printf("\nReceived no data from server\n");
797
798
799         signal(SIGPIPE, SIG_IGN);
800
801         FILE* less; 
802         if(!is_from_script) less = popen( "less -EX", "w");
803         else less = stdout;
804
805         if( less == NULL ) { less = stdout; }
806
807         growing_buffer* resp_buffer = buffer_init(4096);
808
809         while(omsg) {
810
811                 if(raw_print) {
812
813                         if(omsg->_result_content) {
814         
815                                 osrfMessageFree(last_result);
816                                 last_result = omsg;
817         
818                                 char* content;
819         
820                                 if( pretty_print ) {
821                                         char* j = jsonObjectToJSON(omsg->_result_content);
822                                         if( j ) {
823                                                 content = jsonFormatString(j);
824                                                 free(j);
825                                         } else
826                                                 content = strdup( "(null)" );
827                                 } else {
828                                         content = jsonObjectToJSON(omsg->_result_content);
829                                         if( ! content )
830                                                 content = strdup( "(null)" );
831                                 }
832
833                                 printf( "\nReceived Data: %s\n", content );
834                                 free(content);
835         
836                         } else {
837
838                                 char code[16];
839                                 snprintf( code, sizeof(code), "%d", omsg->status_code );
840                                 buffer_add( resp_buffer, code );
841
842                                 printf( "\nReceived Exception:\nName: %s\nStatus: %s\nStatus: %s\n", 
843                                                 omsg->status_name, omsg->status_text, code );
844
845                                 fflush(stdout);
846                                 osrfMessageFree(omsg);
847                         }
848
849                 } else {
850
851                         if(omsg->_result_content) {
852         
853                                 osrfMessageFree(last_result);
854                                 last_result = omsg;
855         
856                                 char* content;
857         
858                                 if( pretty_print && omsg->_result_content ) {
859                                         char* j = jsonObjectToJSON(omsg->_result_content);
860                                         if( j ) {
861                                                 content = jsonFormatString(j);
862                                                 free(j);
863                                         } else
864                                                 content = strdup( "(null)" );
865                                 } else {
866                                         content = jsonObjectToJSON(omsg->_result_content);
867                                         if( ! content )
868                                                 content = strdup( "(null)" );
869                                 }
870
871                                 buffer_add( resp_buffer, "\nReceived Data: " );
872                                 buffer_add( resp_buffer, content );
873                                 buffer_add_char( resp_buffer, '\n' );
874                                 free(content);
875
876                         } else {
877
878                                 buffer_add( resp_buffer, "\nReceived Exception:\nName: " );
879                                 buffer_add( resp_buffer, omsg->status_name );
880                                 buffer_add( resp_buffer, "\nStatus: " );
881                                 buffer_add( resp_buffer, omsg->status_text );
882                                 buffer_add( resp_buffer, "\nStatus: " );
883                                 char code[16];
884                                 snprintf( code, sizeof(code), "%d", omsg->status_code );
885                                 buffer_add( resp_buffer, code );
886                                 osrfMessageFree(omsg);
887                         }
888                 }
889
890                 omsg = osrfAppSessionRequestRecv( session, req_id, recv_timeout );
891
892         }
893
894         double end = get_timestamp_millis();
895
896         fputs( resp_buffer->buf, less );
897         buffer_free( resp_buffer );
898         fputs("\n------------------------------------\n", less);
899         if( osrf_app_session_request_complete( session, req_id ))
900                 fputs("Request Completed Successfully\n", less);
901
902
903         fprintf(less, "Request Time in seconds: %.6f\n", end - start );
904         fputs("------------------------------------\n", less);
905
906         pclose(less);
907
908         osrf_app_session_request_finish( session, req_id );
909
910         if( session_is_temporary ) {
911                 osrf_app_session_disconnect( session );
912                 osrfAppSessionFree( session );
913         }
914
915         return 1;
916
917
918 }
919
920 static int handle_time( const osrfStringArray* cmd_array ) {
921
922         const char* word_1 = osrfStringArrayGetString( cmd_array, 1 );
923         if( !word_1 ) {
924                 printf("%f\n", get_timestamp_millis());
925         } else {
926                 time_t epoch = (time_t) atoi( word_1 );
927                 printf("%s", ctime(&epoch));
928         }
929         return 1;
930 }
931
932
933 static int router_query_servers( const char* router_server ) {
934
935         if( ! router_server || strlen(router_server) == 0 ) 
936                 return 0;
937
938         const static char router_text[] = "router@%s/router";
939         size_t len = sizeof( router_text ) + strlen( router_server ) + 1;
940         char rbuf[len];
941         snprintf(rbuf, sizeof(rbuf), router_text, router_server );
942                 
943         transport_message* send = 
944                 message_init( "servers", NULL, NULL, rbuf, NULL );
945         message_set_router_info( send, NULL, NULL, NULL, "query", 0 );
946
947         client_send_message( client, send );
948         message_free( send );
949
950         transport_message* recv = client_recv( client, -1 );
951         if( recv == NULL ) {
952                 fprintf(stderr, "NULL message received from router\n");
953                 return 1;
954         }
955         
956         printf( 
957                         "---------------------------------------------------------------------------------\n"
958                         "Received from 'server' query on %s\n"
959                         "---------------------------------------------------------------------------------\n"
960                         "original reg time | latest reg time | last used time | class | server\n"
961                         "---------------------------------------------------------------------------------\n"
962                         "%s"
963                         "---------------------------------------------------------------------------------\n"
964                         , router_server, recv->body );
965
966         message_free( recv );
967         
968         return 1;
969 }
970
971 static int print_help( void ) {
972
973         fputs(
974                         "---------------------------------------------------------------------------------\n"
975                         "General commands:\n"
976                         "---------------------------------------------------------------------------------\n"
977                         "help                   - Display this message\n"
978                         "!<command> [args]      - Forks and runs the given command in the shell\n"
979                 /*
980                         "time                   - Prints the current time\n"
981                         "time <timestamp>       - Formats seconds since epoch into readable format\n"
982                 */
983                         "set <variable> <value> - Set a srfsh variable (e.g. set pretty_print true )\n"
984                         "print <variable>       - Displays the value of a srfsh variable\n"
985                         "\n"
986                         "---------------------------------------------------------------------------------\n"
987                         "Variables:\n"
988                         "---------------------------------------------------------------------------------\n"
989                         "pretty_print            - Display nicely formatted JSON results\n"
990                         "       - Accepted values: true, false\n"
991                         "       - Default value: true\n"
992                         "\n"
993                         "raw_print               - Pass JSON results through 'less' paging command\n"
994                         "       - Accepted values: true, false\n"
995                         "       - Default value: false\n"
996                         "\n"
997                         "---------------------------------------------------------------------------------\n"
998                         "Commands for OpenSRF services and methods:\n"
999                         "---------------------------------------------------------------------------------\n"
1000                         "introspect <service> [\"method-name\"]\n"
1001                         "       - Prints service API, limited to the methods that match the optional\n"
1002                         "                right-truncated method-name parameter\n"
1003                         "\n"
1004                         "request <service> <method> [ <JSON formatted string of params> ]\n"
1005                         "       - Anything passed in will be wrapped in a JSON array,\n"
1006                         "               so add commas if there is more than one param\n"
1007                         "\n"
1008                         "router query servers <server1 [, server2, ...]>\n"
1009                         "       - Returns stats on connected services\n"
1010                         "\n"
1011                         "relay <service> <method>\n"
1012                         "       - Performs the requested query using the last received result as the param\n"
1013                         "\n"
1014                         "math_bench <num_batches> [0|1|2]\n"
1015                         "       - 0 means don't reconnect, 1 means reconnect after each batch of 4, and\n"
1016                         "                2 means reconnect after every request\n"
1017                         "\n"
1018                         "---------------------------------------------------------------------------------\n"
1019                         " Commands for Evergreen\n"
1020                         "---------------------------------------------------------------------------------\n"
1021                         "login <username> <password> [type] [org_unit] [workstation]\n"
1022                         "       - Logs into the 'server' and displays the session id\n"
1023                         "       - To view the session id later, enter: print login\n"
1024                         "---------------------------------------------------------------------------------\n"
1025                         "\n"
1026                         "Note: long output is piped through 'less' unless the 'raw_print' variable\n"
1027                         "is true.  To search in 'less', type: /<search>\n"
1028                         "---------------------------------------------------------------------------------\n"
1029                         "\n",
1030                         stdout );
1031
1032         return 1;
1033 }
1034
1035
1036 /*
1037 static char* tabs(int count) {
1038         growing_buffer* buf = buffer_init(24);
1039         int i;
1040         for(i=0;i!=count;i++)
1041                 buffer_add(buf, "  ");
1042
1043         char* final = buffer_data( buf );
1044         buffer_free( buf );
1045         return final;
1046 }
1047 */
1048
1049
1050 static int handle_math( const osrfStringArray* cmd_array ) {
1051         const char* word = osrfStringArrayGetString( cmd_array, 1 );
1052         if( word )
1053                 return do_math( atoi( word ), 0 );
1054         return 0;
1055 }
1056
1057
1058 static int do_math( int count, int style ) {
1059
1060         osrfAppSession* session = osrfAppSessionClientInit( "opensrf.math" );
1061         osrfAppSessionConnect(session);
1062
1063         jsonObject* params = jsonNewObjectType( JSON_ARRAY );
1064         jsonObjectPush(params,jsonNewObject("1"));
1065         jsonObjectPush(params,jsonNewObject("2"));
1066
1067         char* methods[] = { "add", "sub", "mult", "div" };
1068         char* answers[] = { "3", "-1", "2", "0.5" };
1069
1070         float times[ count * 4 ];
1071         memset(times, 0, sizeof(times));
1072
1073         int k;
1074         for(k=0;k!=100;k++) {
1075                 if(!(k%10)) 
1076                         fprintf(stderr,"|");
1077                 else
1078                         fprintf(stderr,".");
1079         }
1080
1081         fprintf(stderr,"\n\n");
1082
1083         int running = 0;
1084         int i;
1085         for(i=0; i!= count; i++) {
1086
1087                 int j;
1088                 for(j=0; j != 4; j++) {
1089
1090                         ++running;
1091
1092                         double start = get_timestamp_millis();
1093                         int req_id = osrfAppSessionSendRequest( session, params, methods[j], 1 );
1094                         osrfMessage* omsg = osrfAppSessionRequestRecv( session, req_id, 5 );
1095                         double end = get_timestamp_millis();
1096
1097                         times[(4*i) + j] = end - start;
1098
1099                         if(omsg) {
1100         
1101                                 if(omsg->_result_content) {
1102                                         char* jsn = jsonObjectToJSON(omsg->_result_content);
1103                                         if(!strcmp(jsn, answers[j]))
1104                                                 fprintf(stderr, "+");
1105                                         else
1106                                                 fprintf(stderr, "\n![%s] - should be %s\n", jsn, answers[j] );
1107                                         free(jsn);
1108                                 }
1109
1110
1111                                 osrfMessageFree(omsg);
1112                 
1113                         } else { fprintf( stderr, "\nempty message for tt: %d\n", req_id ); }
1114
1115                         osrf_app_session_request_finish( session, req_id );
1116
1117                         if(style == 2)
1118                                 osrf_app_session_disconnect( session );
1119
1120                         if(!(running%100))
1121                                 fprintf(stderr,"\n");
1122                 }
1123
1124                 if(style==1)
1125                         osrf_app_session_disconnect( session );
1126         }
1127
1128         osrfAppSessionFree( session );
1129         jsonObjectFree(params);
1130
1131         int c;
1132         float total = 0;
1133         for(c=0; c!= count*4; c++) 
1134                 total += times[c];
1135
1136         float avg = total / (count*4); 
1137         fprintf(stderr, "\n      Average round trip time: %f\n", avg );
1138
1139         return 1;
1140 }
1141
1142 /**
1143         @name Command line parser
1144
1145         This group of functions parses the command line into a series of chunks, and stores
1146         the chunks in an osrfStringArray.
1147
1148         A chunk may consist of a JSON string, complete with square brackets, curly braces, and
1149         embedded white space.  It wouldn't work simply to break up the line into  tokens
1150         separated by white space.  Sometimes white space separates chunks, and sometimes it
1151         occurs within a chunk.
1152
1153         When it sees a left square bracket or curly brace, the parser goes into JSON mode,
1154         collecting characters up to the corresponding right square bracket or curly brace.
1155         It also eliminates most kinds of unnecessary white space.
1156
1157         The JSON parsing is rudimentary.  It does not validate the syntax -- it merely looks
1158         for the end of the JSON string.  Eventually the JSON string will be passed to a real
1159         JSON parser, which will detect and report syntax errors.
1160
1161         When not in JSON mode, the parser collects tokens separated by white space.  It also
1162         collects character strings in quotation marks, possibly including embedded white space.
1163         Within a quoted string, an embedded quotation mark does not terminate the string if it
1164         is escaped by a preceding backslash.
1165 */
1166
1167 /**
1168         @brief Collect a string literal enclosed by quotation marks.
1169         @param parser Pointer to an ArgParser
1170
1171         A quotation mark serves as a terminator unless it is escaped by a preceding backslash.
1172         In the latter case, we collect both the backslash and the escaped quotation mark.
1173 */
1174 static void get_string_literal( ArgParser* parser ) {
1175         // Collect character until the first unescaped quotation mark, or EOL
1176         do {
1177                 OSRF_BUFFER_ADD_CHAR( parser->buf, *parser->itr );
1178
1179                 // Don't stop at a quotation mark if it's escaped
1180                 if( '\\' == *parser->itr && '\"' == *( parser->itr + 1 ) ) {
1181                                 OSRF_BUFFER_ADD_CHAR( parser->buf, '\"' );
1182                                 ++parser->itr;
1183                 }
1184
1185                 ++parser->itr;
1186         } while( *parser->itr && *parser->itr != '\"' );
1187
1188         OSRF_BUFFER_ADD_CHAR( parser->buf, '\"' );
1189         ++parser->itr;
1190 }
1191
1192 /**
1193         @brief Collect a JSON array (enclosed by square brackets).
1194         @param parser Pointer to an ArgParser.
1195
1196         Collect characters until you find the closing square bracket.  Collect any intervening
1197         JSON arrays, JSON objects, or string literals recursively.
1198  */
1199 static void get_json_array( ArgParser* parser ) {
1200
1201         OSRF_BUFFER_ADD_CHAR( parser->buf, '[' );
1202         ++parser->itr;
1203
1204         // Collect characters through the closing square bracket
1205         while( *parser->itr != ']' ) {
1206
1207                 if( '\"' == *parser->itr ) {
1208                         get_string_literal( parser );
1209                 } else if( '[' == *parser->itr ) {
1210                         get_json_array( parser );
1211                 } else if( '{' == *parser->itr ) {
1212                         get_json_object( parser );
1213                 } else if( isspace( (unsigned char) *parser->itr ) ) {
1214                         ++parser->itr;   // Ignore white space
1215                 } else if ( '\0' == *parser->itr ) {
1216                         return;   // Ignore failure to close the object
1217                 } else {
1218                         get_misc( parser );
1219                         // make sure that bare words don't run together
1220                         OSRF_BUFFER_ADD_CHAR( parser->buf, ' ' );
1221                 }
1222         } // end while
1223
1224         OSRF_BUFFER_ADD_CHAR( parser->buf, ']' );
1225         ++parser->itr;
1226 }
1227
1228 /**
1229         @brief Collect a JSON object (enclosed by curly braces).
1230         @param parser Pointer to an ArgParser.
1231
1232         Collect characters until you find the closing curly brace.  Collect any intervening
1233         JSON arrays, JSON objects, or string literals recursively.
1234  */
1235 static void get_json_object( ArgParser* parser ) {
1236
1237         OSRF_BUFFER_ADD_CHAR( parser->buf, '{' );
1238         ++parser->itr;
1239
1240         // Collect characters through the closing curly brace
1241         while( *parser->itr != '}' ) {
1242
1243                 if( '\"' == *parser->itr ) {
1244                         get_string_literal( parser );
1245                 } else if( '[' == *parser->itr ) {
1246                         get_json_array( parser );
1247                 } else if( '{' == *parser->itr ) {
1248                         get_json_object( parser );
1249                 } else if( isspace( (unsigned char) *parser->itr ) ) {
1250                         ++parser->itr;   // Ignore white space
1251                 } else if ( '\0' == *parser->itr ) {
1252                         return;   // Ignore failure to close the object
1253                 } else {
1254                         get_misc( parser );
1255                         // make sure that bare words don't run together
1256                         OSRF_BUFFER_ADD_CHAR( parser->buf, ' ' );
1257                 }
1258         } // end while
1259
1260         OSRF_BUFFER_ADD_CHAR( parser->buf, '}' );
1261         ++parser->itr;
1262 }
1263
1264 /**
1265         @brief Collect a token terminated by white space or a ']' or '}' character.
1266         @param parser Pointer to an ArgParser
1267
1268         For valid JSON, the chunk collected here would be either a number or one of the
1269         JSON key words "null", "true", or "false".  However at this stage we're not finicky.
1270         We just collect whatever we see until we find a terminator.
1271 */
1272 static void get_misc( ArgParser* parser ) {
1273         // Collect characters until we see one that doesn't belong
1274         while( 1 ) {
1275                 OSRF_BUFFER_ADD_CHAR( parser->buf, *parser->itr );
1276                 ++parser->itr;
1277                 char c = *parser->itr;
1278                 if( '\0' == c || isspace( (unsigned char) c ) 
1279                         || '{' == c || '}' == c || '[' == c || ']' == c || '\"' == c ) {
1280                         break;
1281                 }
1282         }
1283 }
1284
1285 /**
1286         @brief Parse the command line.
1287         @param request Pointer to the command line
1288         @param cmd_array Pointer to an osrfStringArray to hold the output of the parser.
1289
1290         The parser operates by recursive descent.  We build each chunk of command line in a
1291         growing_buffer belonging to an ArgParser, and then load the chunk into a slot in an
1292         osrfStringArray supplied by the calling code.
1293 */
1294 static void parse_args( const char* request, osrfStringArray* cmd_array )
1295 {
1296         ArgParser parser;
1297
1298         // Initialize the ArgParser
1299         parser.itr = request;
1300         parser.buf = buffer_init( 128 );
1301
1302         int done = 0;               // boolean
1303         while( !done ) {
1304                 OSRF_BUFFER_RESET( parser.buf );
1305
1306                 // skip any white space or commas
1307                 while( *parser.itr
1308                            && ( isspace( (unsigned char) *parser.itr ) || ',' == *parser.itr ) )
1309                         ++parser.itr;
1310
1311                 if( '\0' == *parser.itr )
1312                         done = 1;
1313                 else if( '{' == *parser.itr ) {
1314                         // Load a JSON object
1315                         get_json_object( &parser );
1316                 } else if( '[' == *parser.itr ) {
1317                         // Load a JSON array
1318                         get_json_array( &parser );
1319                 } else if( '\"' == *parser.itr ) {
1320                         // Load a string literal
1321                         get_string_literal( &parser );
1322                 } else {
1323                         // Anything else is delimited by white space
1324                         do {
1325                                 OSRF_BUFFER_ADD_CHAR( parser.buf, *parser.itr );
1326                                 ++parser.itr;
1327                         } while( *parser.itr && ! isspace( (unsigned char) *parser.itr ) );
1328                 }
1329
1330                 // Remove a trailing comma, if present
1331                 char lastc = OSRF_BUFFER_C_STR( parser.buf )[
1332                         strlen( OSRF_BUFFER_C_STR( parser.buf )  ) - 1 ];
1333                 if( ',' == lastc )
1334                         buffer_chomp( parser.buf );
1335
1336                 // Add the chunk to the osrfStringArray
1337                 const char* s = OSRF_BUFFER_C_STR( parser.buf );
1338                 if( s && *s ) {
1339                         osrfStringArrayAdd( cmd_array, s );
1340                 }
1341         }
1342
1343         buffer_free( parser.buf );
1344 }
1345 /*@}*/
1346