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