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