]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/srfsh/srfsh.c
LP#1436047: Allow disabling of "bang commands" in srfsh
[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) system( request + 1 );
380                 ret_val = 1;
381         }
382
383         osrfStringArrayFree( cmd_array );
384
385         if(!ret_val)
386                 return parse_error( request );
387         else
388                 return 1;
389 }
390
391
392 static int handle_introspect( const osrfStringArray* cmd_array ) {
393
394         const char* service = osrfStringArrayGetString( cmd_array, 1 );
395         if( ! service )
396                 return 0;
397
398         fprintf(stderr, "--> %s\n", service );
399
400         // Build a command in a suitably-sized
401         // buffer and then parse it
402
403         size_t len;
404         const char* method = osrfStringArrayGetString( cmd_array, 2 );
405         if( method ) {
406                 static const char text[] = "request %s opensrf.system.method %s";
407                 len = sizeof( text ) + strlen( service ) + strlen( method );
408                 char buf[len];
409                 snprintf( buf, sizeof(buf), text, service, method );
410                 return process_request( buf );
411
412         } else {
413                 static const char text[] = "request %s opensrf.system.method.all";
414                 len = sizeof( text ) + strlen( service );
415                 char buf[len];
416                 snprintf( buf, sizeof(buf), text, service );
417                 return process_request( buf );
418
419         }
420 }
421
422
423 static int handle_login( const osrfStringArray* cmd_array ) {
424
425         const char* username = osrfStringArrayGetString( cmd_array, 1 );
426         const char* password = osrfStringArrayGetString( cmd_array, 2 );
427
428         if( username && password ) {
429
430                 const char* type                = osrfStringArrayGetString( cmd_array, 3 );
431                 const char* orgloc              = osrfStringArrayGetString( cmd_array, 4 );
432                 const char* workstation = osrfStringArrayGetString( cmd_array, 5 );
433                 int orgloci = (orgloc) ? atoi(orgloc) : 0;
434                 if(!type) type = "opac";
435
436                 char login_text[] = "request open-ils.auth open-ils.auth.authenticate.init \"%s\"";
437                 size_t len = sizeof( login_text ) + strlen(username) + 1;
438
439                 char buf[len];
440                 snprintf( buf, sizeof(buf), login_text, username );
441                 process_request(buf);
442
443                 const char* hash;
444                 if(last_result && last_result->_result_content) {
445                         jsonObject* r = last_result->_result_content;
446                         hash = jsonObjectGetString(r);
447                 } else return 0;
448
449                 char* pass_buf = md5sum(password);
450
451                 size_t both_len = strlen( hash ) + strlen( pass_buf ) + 1;
452                 char both_buf[both_len];
453                 snprintf(both_buf, sizeof(both_buf), "%s%s", hash, pass_buf);
454
455                 char* mess_buf = md5sum(both_buf);
456
457                 growing_buffer* argbuf = buffer_init(64);
458                 buffer_fadd(argbuf, 
459                                 "request open-ils.auth open-ils.auth.authenticate.complete "
460                                 "{ \"username\" : \"%s\", \"password\" : \"%s\"", username, mess_buf );
461
462                 if(type) buffer_fadd( argbuf, ", \"type\" : \"%s\"", type );
463                 if(orgloci) buffer_fadd( argbuf, ", \"org\" : %d", orgloci );
464                 if(workstation) buffer_fadd( argbuf, ", \"workstation\" : \"%s\"", workstation);
465                 buffer_add_char( argbuf, '}' );
466
467                 free(pass_buf);
468                 free(mess_buf);
469
470                 process_request( argbuf->buf );
471                 buffer_free(argbuf);
472
473                 if( login_session != NULL )
474                         free( login_session );
475
476                 const jsonObject* x = last_result->_result_content;
477                 double authtime = 0;
478                 if(x) {
479                         const char* authtoken = jsonObjectGetString(
480                                         jsonObjectGetKeyConst(jsonObjectGetKeyConst(x,"payload"), "authtoken"));
481                         authtime  = jsonObjectGetNumber(
482                                         jsonObjectGetKeyConst(jsonObjectGetKeyConst(x,"payload"), "authtime"));
483
484                         if(authtoken)
485                                 login_session = strdup(authtoken);
486                         else
487                                 login_session = NULL;
488                 }
489                 else login_session = NULL;
490
491                 printf("Login Session: %s.  Session timeout: %f\n",
492                            (login_session ? login_session : "(none)"), authtime );
493                 
494                 return 1;
495
496         }
497
498         return 0;
499 }
500
501 /**
502         @brief Open connections to one or more specified services.
503         @param cmd_array Pointer to a list of command line chunks.
504         @return 1 in all cases.
505
506         The first chunk of the command line is the "open" command.  Subsequent chunks, if any,
507         are server names.
508
509         Try to open all specified servers.  If no servers are specified, report what servers are
510         currently open.
511 */
512 static int handle_open( const osrfStringArray* cmd_array ) {
513         if( NULL == osrfStringArrayGetString( cmd_array, 1 ) ) {
514                 if( ! server_hash || osrfHashGetCount( server_hash ) == 0 ) {
515                         printf( "No services are currently open\n" );
516                         return 1;
517                 }
518
519                 printf( "Service(s) currently open:\n" );
520
521                 osrfHashIterator* itr = osrfNewHashIterator( server_hash );
522                 while( osrfHashIteratorNext( itr ) ) {
523                         printf( "\t%s\n", osrfHashIteratorKey( itr ) );
524                 }
525                 osrfHashIteratorFree( itr );
526                 return 1;
527         }
528
529         if( ! server_hash )
530                 server_hash = osrfNewHash( 6 );
531
532         int i;
533         for( i = 1; ; ++i ) {    // for each requested service
534                 const char* server = osrfStringArrayGetString( cmd_array, i );
535                 if( ! server )
536                         break;
537
538                 if( osrfHashGet( server_hash, server ) ) {
539                         printf( "Service %s is already open\n", server );
540                         continue;
541                 }
542
543                 // Try to open a session with the current specified server
544                 osrfAppSession* session = osrfAppSessionClientInit(server);
545
546                 if(!osrfAppSessionConnect(session)) {
547                         fprintf(stderr, "Unable to open service %s\n", server);
548                         osrfLogWarning( OSRF_LOG_MARK, "Unable to open remote service %s\n", server );
549                         osrfAppSessionFree( session );
550                 } else {
551                         osrfHashSet( server_hash, session, server );
552                         printf( "Service %s opened\n", server );
553                 }
554         }
555
556         return 1;
557 }
558
559 /**
560         @brief Close connections to one or more specified services.
561         @param cmd_array Pointer to a list of command line chunks.
562         @return 1 if any services were closed, or 0 if there were none to close.
563
564         The first chunk of the command line is the "close" command.  Subsequent chunks, if any,
565         are server names.
566 */
567 static int handle_close( const osrfStringArray* cmd_array ) {
568         if( cmd_array->size < 2 ) {
569                 fprintf( stderr, "No service specified for close\n" );
570                 return 0;
571         }
572
573         int i;
574         for( i = 1; ; ++i ) {
575                 const char* server = osrfStringArrayGetString( cmd_array, i );
576                 if( ! server )
577                         break;
578
579                 osrfAppSession* session = osrfHashRemove( server_hash, server );
580                 if( ! session ) {
581                         printf( "Service \"%s\" is not open\n", server );
582                         continue;
583                 }
584
585                 osrf_app_session_disconnect( session );
586                 osrfAppSessionFree( session );
587                 printf( "Service \"%s\" closed\n", server );
588         }
589
590         return 1;
591 }
592
593 /**
594         @brief Close all currently open connections to services.
595  */
596 static void close_all_sessions( void ) {
597
598         osrfAppSession* session;
599         osrfHashIterator* itr = osrfNewHashIterator( server_hash );
600
601         while(( session = osrfHashIteratorNext( itr ) )) {
602                 osrf_app_session_disconnect( session );
603                 osrfAppSessionFree( session );
604         }
605
606         osrfHashIteratorFree( itr );
607 }
608
609 static int handle_set( const osrfStringArray* cmd_array ) {
610
611         const char* variable = osrfStringArrayGetString( cmd_array, 1 );
612         if( variable ) {
613
614                 const char* val = osrfStringArrayGetString( cmd_array, 2 );
615                 if( val ) {
616
617                         if(!strcmp(variable,"pretty_print")) {
618                                 if(!strcmp(val,"true")) {
619                                         pretty_print = 1;
620                                         printf("pretty_print = true\n");
621                                         return 1;
622                                 } else if(!strcmp(val,"false")) {
623                                         pretty_print = 0;
624                                         printf("pretty_print = false\n");
625                                         return 1;
626                                 }
627                         }
628
629                         if(!strcmp(variable,"raw_print")) {
630                                 if(!strcmp(val,"true")) {
631                                         raw_print = 1;
632                                         printf("raw_print = true\n");
633                                         return 1;
634                                 } else if(!strcmp(val,"false")) {
635                                         raw_print = 0;
636                                         printf("raw_print = false\n");
637                                         return 1;
638                                 }
639                         }
640
641                 }
642         }
643
644         return 0;
645 }
646
647
648 static int handle_print( const osrfStringArray* cmd_array ) {
649
650         const char* variable = osrfStringArrayGetString( cmd_array, 1 );
651
652         if( variable ) {
653                 if(!strcmp(variable,"pretty_print")) {
654                         if(pretty_print) {
655                                 printf("pretty_print = true\n");
656                                 return 1;
657                         } else {
658                                 printf("pretty_print = false\n");
659                                 return 1;
660                         }
661                 }
662
663                 if(!strcmp(variable,"raw_print")) {
664                         if(raw_print) {
665                                 printf("raw_print = true\n");
666                                 return 1;
667                         } else {
668                                 printf("raw_print = false\n");
669                                 return 1;
670                         }
671                 }
672
673                 if(!strcmp(variable,"login")) {
674                         printf("login session = %s\n",
675                                    login_session ? login_session : "(none)" );
676                         return 1;
677                 }
678
679         }
680         return 0;
681 }
682
683 static int handle_router( const osrfStringArray* cmd_array ) {
684
685         if(!client)
686                 return 1;
687
688         int i;
689
690         const char* word_1 = osrfStringArrayGetString( cmd_array, 1 );
691         const char* word_2 = osrfStringArrayGetString( cmd_array, 2 );
692         if( word_1 ) {
693                 if( !strcmp( word_1,"query") ) {
694
695                         if( word_2 && !strcmp( word_2, "servers" ) ) {
696                                 for( i=3; i < COMMAND_BUFSIZE - 3; i++ ) {
697                                         const char* word = osrfStringArrayGetString( cmd_array, i );
698                                         if( word )
699                                                 router_query_servers( word );
700                                         else
701                                                 break;
702                                 }
703                                 return 1;
704                         }
705                         return 0;
706                 }
707                 return 0;
708         }
709         return 0;
710 }
711
712
713
714 static int handle_request( const osrfStringArray* cmd_array, int relay ) {
715
716         if(!client)
717                 return 1;
718
719         const char* server = osrfStringArrayGetString( cmd_array, 1 );
720         const char* method = osrfStringArrayGetString( cmd_array, 2 );
721
722         if( server ) {
723                 int i;
724                 growing_buffer* buffer = NULL;
725                 if( !relay ) {
726                         int first = 1;   // boolean
727                         buffer = buffer_init( 128 );
728                         buffer_add_char( buffer, '[' );
729                         for(i = 3; ; i++ ) {
730                                 const char* word = osrfStringArrayGetString( cmd_array, i );
731                                 if( !word )
732                                         break;
733
734                                 if( first )
735                                         first = 0;
736                                 else
737                                         buffer_add( buffer, ", " );
738
739                                 buffer_add( buffer, word );
740
741                                 /* remove trailing semicolon if user accidentally entered it */
742                                 if( word[ strlen( word ) - 1 ] == ';' )
743                                         buffer_chomp( buffer );
744                         }
745                         buffer_add_char( buffer, ']' );
746                 }
747
748                 int rc = send_request( server, method, buffer, relay );
749                 buffer_free( buffer );
750                 return rc;
751         } 
752
753         return 0;
754 }
755
756 int send_request( const char* server,
757                 const char* method, growing_buffer* buffer, int relay ) {
758         if( server == NULL || method == NULL )
759                 return 0;
760
761         jsonObject* params = NULL;
762         if( !relay ) {
763                 if( buffer != NULL && buffer->n_used > 0 ) {
764                         // Temporarily redirect parsing error messages to stderr
765                         osrfLogToStderr();
766                         params = jsonParse( OSRF_BUFFER_C_STR( buffer ) );
767                         osrfRestoreLogType();
768                 }
769         } else {
770                 if(!last_result || ! last_result->_result_content) { 
771                         printf("We're not going to call 'relay' with no result params\n");
772                         return 1;
773                 }
774                 else {
775                         params = jsonNewObject(NULL);
776                         jsonObjectPush(params, last_result->_result_content );
777                 }
778         }
779
780         if(buffer->n_used > 0 && params == NULL) {
781                 fprintf(stderr, "JSON error detected, not executing\n");
782                 jsonObjectFree(params);
783                 return 1;
784         }
785
786         int session_is_temporary;    // boolean
787         osrfAppSession* session = osrfHashGet( server_hash, server );
788         if( session ) {
789                 session_is_temporary = 0;     // use an existing session
790         } else {
791                 session = osrfAppSessionClientInit(server);   // open a session
792                 session_is_temporary = 1;                     // just for this request
793         }
794
795         double start = get_timestamp_millis();
796
797         int req_id = osrfAppSessionSendRequest( session, params, method, 1 );
798         if( -1 == req_id ) {
799                 fprintf(stderr, "Unable to communicate with service %s\n", server);
800                 osrfLogWarning( OSRF_LOG_MARK,
801                                 "Unable to communicate with remote service %s\n", server );
802                 osrfAppSessionFree( session );
803                 jsonObjectFree(params);
804                 return 1;
805         }
806         jsonObjectFree(params);
807
808         osrfMessage* omsg = osrfAppSessionRequestRecv( session, req_id, recv_timeout );
809
810         if(!omsg) 
811                 printf("\nReceived no data from server\n");
812
813         signal(SIGPIPE, SIG_IGN);
814
815         FILE* less; 
816         if(!is_from_script) less = popen( "less -EX", "w");
817         else less = stdout;
818
819         if( less == NULL ) { less = stdout; }
820
821         growing_buffer* resp_buffer = buffer_init(4096);
822
823         while(omsg) {
824
825                 if(raw_print) {
826
827                         if(omsg->_result_content) {
828         
829                                 osrfMessageFree(last_result);
830                                 last_result = omsg;
831         
832                                 char* content;
833         
834                                 if( pretty_print ) {
835                                         char* j = jsonObjectToJSON(omsg->_result_content);
836                                         if( j ) {
837                                                 content = jsonFormatString(j);
838                                                 free(j);
839                                         } else
840                                                 content = strdup( "(null)" );
841                                 } else {
842                                         content = jsonObjectToJSON(omsg->_result_content);
843                                         if( ! content )
844                                                 content = strdup( "(null)" );
845                                 }
846
847                                 printf( "\nReceived Data: %s\n", content );
848                                 free(content);
849         
850                         } else {
851
852                                 char code[16];
853                                 snprintf( code, sizeof(code), "%d", omsg->status_code );
854                                 buffer_add( resp_buffer, code );
855
856                                 printf( "\nReceived Exception:\nName: %s\nStatus: %s\nStatus: %s\n", 
857                                                 omsg->status_name, omsg->status_text, code );
858
859                                 fflush(stdout);
860                                 osrfMessageFree(omsg);
861                         }
862
863                 } else {
864
865                         if(omsg->_result_content) {
866         
867                                 osrfMessageFree(last_result);
868                                 last_result = omsg;
869         
870                                 char* content;
871         
872                                 if( pretty_print && omsg->_result_content ) {
873                                         char* j = jsonObjectToJSON(omsg->_result_content);
874                                         if( j ) {
875                                                 content = jsonFormatString(j);
876                                                 free(j);
877                                         } else
878                                                 content = strdup( "(null)" );
879                                 } else {
880                                         content = jsonObjectToJSON(omsg->_result_content);
881                                         if( ! content )
882                                                 content = strdup( "(null)" );
883                                 }
884
885                                 buffer_add( resp_buffer, "\nReceived Data: " );
886                                 buffer_add( resp_buffer, content );
887                                 buffer_add_char( resp_buffer, '\n' );
888                                 free(content);
889
890                         } else {
891
892                                 buffer_add( resp_buffer, "\nReceived Exception:\nName: " );
893                                 buffer_add( resp_buffer, omsg->status_name );
894                                 buffer_add( resp_buffer, "\nStatus: " );
895                                 buffer_add( resp_buffer, omsg->status_text );
896                                 buffer_add( resp_buffer, "\nStatus: " );
897                                 char code[16];
898                                 snprintf( code, sizeof(code), "%d", omsg->status_code );
899                                 buffer_add( resp_buffer, code );
900                                 osrfMessageFree(omsg);
901                         }
902                 }
903
904                 omsg = osrfAppSessionRequestRecv( session, req_id, recv_timeout );
905
906         }
907
908         double end = get_timestamp_millis();
909
910         fputs( resp_buffer->buf, less );
911         buffer_free( resp_buffer );
912         fputs("\n------------------------------------\n", less);
913         if( osrf_app_session_request_complete( session, req_id ))
914                 fputs("Request Completed Successfully\n", less);
915
916         fprintf(less, "Request Time in seconds: %.6f\n", end - start );
917         fputs("------------------------------------\n", less);
918
919         pclose(less);
920
921         osrf_app_session_request_finish( session, req_id );
922
923         if( session_is_temporary )
924                 osrfAppSessionFree( session );
925
926         return 1;
927
928
929 }
930
931 static int handle_time( const osrfStringArray* cmd_array ) {
932
933         const char* word_1 = osrfStringArrayGetString( cmd_array, 1 );
934         if( !word_1 ) {
935                 printf("%f\n", get_timestamp_millis());
936         } else {
937                 time_t epoch = (time_t) atoi( word_1 );
938                 printf("%s", ctime(&epoch));
939         }
940         return 1;
941 }
942
943
944 static int router_query_servers( const char* router_server ) {
945
946         if( ! router_server || strlen(router_server) == 0 ) 
947                 return 0;
948
949         const static char router_text[] = "router@%s/router";
950         size_t len = sizeof( router_text ) + strlen( router_server ) + 1;
951         char rbuf[len];
952         snprintf(rbuf, sizeof(rbuf), router_text, router_server );
953                 
954         transport_message* send = 
955                 message_init( "servers", NULL, NULL, rbuf, NULL );
956         message_set_router_info( send, NULL, NULL, NULL, "query", 0 );
957
958         client_send_message( client, send );
959         message_free( send );
960
961         transport_message* recv = client_recv( client, -1 );
962         if( recv == NULL ) {
963                 fprintf(stderr, "NULL message received from router\n");
964                 return 1;
965         }
966         
967         printf( 
968                         "---------------------------------------------------------------------------------\n"
969                         "Received from 'server' query on %s\n"
970                         "---------------------------------------------------------------------------------\n"
971                         "original reg time | latest reg time | last used time | class | server\n"
972                         "---------------------------------------------------------------------------------\n"
973                         "%s"
974                         "---------------------------------------------------------------------------------\n"
975                         , router_server, recv->body );
976
977         message_free( recv );
978         
979         return 1;
980 }
981
982 static int print_help( void ) {
983
984         fputs(
985                         "---------------------------------------------------------------------------------\n"
986                         "General commands:\n"
987                         "---------------------------------------------------------------------------------\n"
988                         "help                   - Display this message\n"
989                         "!<command> [args]      - Forks and runs the given command in the shell\n"
990                 /*
991                         "time                   - Prints the current time\n"
992                         "time <timestamp>       - Formats seconds since epoch into readable format\n"
993                 */
994                         "set <variable> <value> - Set a srfsh variable (e.g. set pretty_print true )\n"
995                         "print <variable>       - Displays the value of a srfsh variable\n"
996                         "\n"
997                         "---------------------------------------------------------------------------------\n"
998                         "Variables:\n"
999                         "---------------------------------------------------------------------------------\n"
1000                         "pretty_print            - Display nicely formatted JSON results\n"
1001                         "       - Accepted values: true, false\n"
1002                         "       - Default value: true\n"
1003                         "\n"
1004                         "raw_print               - Pass JSON results through 'less' paging command\n"
1005                         "       - Accepted values: true, false\n"
1006                         "       - Default value: false\n"
1007                         "\n"
1008                         "---------------------------------------------------------------------------------\n"
1009                         "Commands for OpenSRF services and methods:\n"
1010                         "---------------------------------------------------------------------------------\n"
1011                         "introspect <service> [\"method-name\"]\n"
1012                         "       - Prints service API, limited to the methods that match the optional\n"
1013                         "                right-truncated method-name parameter\n"
1014                         "\n"
1015                         "request <service> <method> [ <JSON formatted string of params> ]\n"
1016                         "       - Anything passed in will be wrapped in a JSON array,\n"
1017                         "               so add commas if there is more than one param\n"
1018                         "\n"
1019                         "router query servers <server1 [, server2, ...]>\n"
1020                         "       - Returns stats on connected services\n"
1021                         "\n"
1022                         "relay <service> <method>\n"
1023                         "       - Performs the requested query using the last received result as the param\n"
1024                         "\n"
1025                         "math_bench <num_batches> [0|1|2]\n"
1026                         "       - 0 means don't reconnect, 1 means reconnect after each batch of 4, and\n"
1027                         "                2 means reconnect after every request\n"
1028                         "\n"
1029                         "---------------------------------------------------------------------------------\n"
1030                         " Commands for Evergreen\n"
1031                         "---------------------------------------------------------------------------------\n"
1032                         "login <username> <password> [type] [org_unit] [workstation]\n"
1033                         "       - Logs into the 'server' and displays the session id\n"
1034                         "       - To view the session id later, enter: print login\n"
1035                         "---------------------------------------------------------------------------------\n"
1036                         "\n"
1037                         "Note: long output is piped through 'less' unless the 'raw_print' variable\n"
1038                         "is true.  To search in 'less', type: /<search>\n"
1039                         "---------------------------------------------------------------------------------\n"
1040                         "\n",
1041                         stdout );
1042
1043         return 1;
1044 }
1045
1046
1047 /*
1048 static char* tabs(int count) {
1049         growing_buffer* buf = buffer_init(24);
1050         int i;
1051         for(i=0;i!=count;i++)
1052                 buffer_add(buf, "  ");
1053
1054         char* final = buffer_data( buf );
1055         buffer_free( buf );
1056         return final;
1057 }
1058 */
1059
1060 /**
1061         @brief Execute the "math_bench" command.
1062         @param cmd_array A list of command arguments.
1063         @return 1 if successful, 0 if not.
1064
1065         The first command argument is required.  It is the number of iterations requested.  If
1066         it is less than 1, it is coerced to 1.
1067
1068         The second command argument is optional, with allowed values of 0 (the default), 1, or 2.
1069         It controls when and whether we call osrf_app_session_disconnect().  If this argument is
1070         out of range, it is coerced to a value of 0 or 2.
1071 */
1072 static int handle_math( const osrfStringArray* cmd_array ) {
1073         const char* word = osrfStringArrayGetString( cmd_array, 1 );
1074         if( word ) {
1075                 int count = atoi( word );
1076                 if( count < 1 )
1077                         count = 1;
1078
1079                 int style = 0;
1080                 const char* style_arg = osrfStringArrayGetString( cmd_array, 2 );
1081                 if( style_arg ) {
1082                         style = atoi( style_arg );
1083                         if( style > 2 )
1084                                 style = 2;
1085                         else if( style < 0 )
1086                                 style = 0;
1087                 }
1088
1089                 return do_math( count, style );
1090         }
1091         return 0;
1092 }
1093
1094
1095 static int do_math( int count, int style ) {
1096
1097         osrfAppSession* session = osrfAppSessionClientInit( "opensrf.math" );
1098         osrfAppSessionConnect(session);
1099
1100         jsonObject* params = jsonNewObjectType( JSON_ARRAY );
1101         jsonObjectPush(params,jsonNewObject("1"));
1102         jsonObjectPush(params,jsonNewObject("2"));
1103
1104         char* methods[] = { "add", "sub", "mult", "div" };
1105         char* answers[] = { "3", "-1", "2", "0.5" };
1106
1107         // Initialize timings to zero.  This shouldn't make a difference, because
1108         // we overwrite each timing anyway before reporting them.
1109         float times[ count * 4 ];
1110         int fi;
1111         for( fi = 0; fi < count; ++fi )
1112                 times[ fi ] = 0.0;
1113
1114         int k;
1115         for(k=0;k!=100;k++) {
1116                 if(!(k%10)) 
1117                         fprintf(stderr,"|");
1118                 else
1119                         fprintf(stderr,".");
1120         }
1121
1122         fprintf(stderr,"\n\n");
1123
1124         int running = 0;
1125         int i;
1126         for(i=0; i!= count; i++) {
1127
1128                 int j;
1129                 for(j=0; j != 4; j++) {
1130
1131                         ++running;
1132
1133                         double start = get_timestamp_millis();
1134                         int req_id = osrfAppSessionSendRequest( session, params, methods[j], 1 );
1135                         osrfMessage* omsg = osrfAppSessionRequestRecv( session, req_id, 5 );
1136                         double end = get_timestamp_millis();
1137
1138                         times[(4*i) + j] = end - start;
1139
1140                         if(omsg) {
1141         
1142                                 if(omsg->_result_content) {
1143                                         char* jsn = jsonObjectToJSON(omsg->_result_content);
1144                                         if(!strcmp(jsn, answers[j]))
1145                                                 fprintf(stderr, "+");
1146                                         else
1147                                                 fprintf(stderr, "\n![%s] - should be %s\n", jsn, answers[j] );
1148                                         free(jsn);
1149                                 }
1150
1151
1152                                 osrfMessageFree(omsg);
1153                 
1154                         } else { fprintf( stderr, "\nempty message for tt: %d\n", req_id ); }
1155
1156                         osrf_app_session_request_finish( session, req_id );
1157
1158                         if(style == 2)
1159                                 osrf_app_session_disconnect( session );
1160
1161                         if(!(running%100))
1162                                 fprintf(stderr,"\n");
1163                 }
1164
1165                 if(style==1)
1166                         osrf_app_session_disconnect( session );
1167         }
1168
1169         osrfAppSessionFree( session );
1170         jsonObjectFree(params);
1171
1172         int c;
1173         float total = 0;
1174         for(c=0; c!= count*4; c++) 
1175                 total += times[c];
1176
1177         float avg = total / (count*4); 
1178         fprintf(stderr, "\n      Average round trip time: %f\n", avg );
1179
1180         return 1;
1181 }
1182
1183 /**
1184         @name Command line parser
1185
1186         This group of functions parses the command line into a series of chunks, and stores
1187         the chunks in an osrfStringArray.
1188
1189         A chunk may consist of a JSON string, complete with square brackets, curly braces, and
1190         embedded white space.  It wouldn't work simply to break up the line into  tokens
1191         separated by white space.  Sometimes white space separates chunks, and sometimes it
1192         occurs within a chunk.
1193
1194         When it sees a left square bracket or curly brace, the parser goes into JSON mode,
1195         collecting characters up to the corresponding right square bracket or curly brace.
1196         It also eliminates most kinds of unnecessary white space.
1197
1198         The JSON parsing is rudimentary.  It does not validate the syntax -- it merely looks
1199         for the end of the JSON string.  Eventually the JSON string will be passed to a real
1200         JSON parser, which will detect and report syntax errors.
1201
1202         When not in JSON mode, the parser collects tokens separated by white space.  It also
1203         collects character strings in quotation marks, possibly including embedded white space.
1204         Within a quoted string, an embedded quotation mark does not terminate the string if it
1205         is escaped by a preceding backslash.
1206 */
1207
1208 /**
1209         @brief Collect a string literal enclosed by quotation marks.
1210         @param parser Pointer to an ArgParser
1211
1212         A quotation mark serves as a terminator unless it is escaped by a preceding backslash.
1213         In the latter case, we collect both the backslash and the escaped quotation mark.
1214 */
1215 static void get_string_literal( ArgParser* parser ) {
1216         // Collect character until the first unescaped quotation mark, or EOL
1217         do {
1218                 OSRF_BUFFER_ADD_CHAR( parser->buf, *parser->itr );
1219
1220                 // Don't stop at a quotation mark if it's escaped
1221                 if( '\\' == *parser->itr && '\"' == *( parser->itr + 1 ) ) {
1222                                 OSRF_BUFFER_ADD_CHAR( parser->buf, '\"' );
1223                                 ++parser->itr;
1224                 }
1225
1226                 ++parser->itr;
1227         } while( *parser->itr && *parser->itr != '\"' );
1228
1229         OSRF_BUFFER_ADD_CHAR( parser->buf, '\"' );
1230         ++parser->itr;
1231 }
1232
1233 /**
1234         @brief Collect a JSON array (enclosed by square brackets).
1235         @param parser Pointer to an ArgParser.
1236
1237         Collect characters until you find the closing square bracket.  Collect any intervening
1238         JSON arrays, JSON objects, or string literals recursively.
1239  */
1240 static void get_json_array( ArgParser* parser ) {
1241
1242         OSRF_BUFFER_ADD_CHAR( parser->buf, '[' );
1243         ++parser->itr;
1244
1245         // Collect characters through the closing square bracket
1246         while( *parser->itr != ']' ) {
1247
1248                 if( '\"' == *parser->itr ) {
1249                         get_string_literal( parser );
1250                 } else if( '[' == *parser->itr ) {
1251                         get_json_array( parser );
1252                 } else if( '{' == *parser->itr ) {
1253                         get_json_object( parser );
1254                 } else if( isspace( (unsigned char) *parser->itr ) ) {
1255                         ++parser->itr;   // Ignore white space
1256                 } else if ( '\0' == *parser->itr ) {
1257                         return;   // Ignore failure to close the object
1258                 } else {
1259                         get_misc( parser );
1260                         // make sure that bare words don't run together
1261                         OSRF_BUFFER_ADD_CHAR( parser->buf, ' ' );
1262                 }
1263         } // end while
1264
1265         OSRF_BUFFER_ADD_CHAR( parser->buf, ']' );
1266         ++parser->itr;
1267 }
1268
1269 /**
1270         @brief Collect a JSON object (enclosed by curly braces).
1271         @param parser Pointer to an ArgParser.
1272
1273         Collect characters until you find the closing curly brace.  Collect any intervening
1274         JSON arrays, JSON objects, or string literals recursively.
1275  */
1276 static void get_json_object( ArgParser* parser ) {
1277
1278         OSRF_BUFFER_ADD_CHAR( parser->buf, '{' );
1279         ++parser->itr;
1280
1281         // Collect characters through the closing curly brace
1282         while( *parser->itr != '}' ) {
1283
1284                 if( '\"' == *parser->itr ) {
1285                         get_string_literal( parser );
1286                 } else if( '[' == *parser->itr ) {
1287                         get_json_array( parser );
1288                 } else if( '{' == *parser->itr ) {
1289                         get_json_object( parser );
1290                 } else if( isspace( (unsigned char) *parser->itr ) ) {
1291                         ++parser->itr;   // Ignore white space
1292                 } else if ( '\0' == *parser->itr ) {
1293                         return;   // Ignore failure to close the object
1294                 } else {
1295                         get_misc( parser );
1296                         // make sure that bare words don't run together
1297                         OSRF_BUFFER_ADD_CHAR( parser->buf, ' ' );
1298                 }
1299         } // end while
1300
1301         OSRF_BUFFER_ADD_CHAR( parser->buf, '}' );
1302         ++parser->itr;
1303 }
1304
1305 /**
1306         @brief Collect a token terminated by white space or a ']' or '}' character.
1307         @param parser Pointer to an ArgParser
1308
1309         For valid JSON, the chunk collected here would be either a number or one of the
1310         JSON key words "null", "true", or "false".  However at this stage we're not finicky.
1311         We just collect whatever we see until we find a terminator.
1312 */
1313 static void get_misc( ArgParser* parser ) {
1314         // Collect characters until we see one that doesn't belong
1315         while( 1 ) {
1316                 OSRF_BUFFER_ADD_CHAR( parser->buf, *parser->itr );
1317                 ++parser->itr;
1318                 char c = *parser->itr;
1319                 if( '\0' == c || isspace( (unsigned char) c ) 
1320                         || '{' == c || '}' == c || '[' == c || ']' == c || '\"' == c ) {
1321                         break;
1322                 }
1323         }
1324 }
1325
1326 /**
1327         @brief Parse the command line.
1328         @param request Pointer to the command line
1329         @param cmd_array Pointer to an osrfStringArray to hold the output of the parser.
1330
1331         The parser operates by recursive descent.  We build each chunk of command line in a
1332         growing_buffer belonging to an ArgParser, and then load the chunk into a slot in an
1333         osrfStringArray supplied by the calling code.
1334 */
1335 static void parse_args( const char* request, osrfStringArray* cmd_array )
1336 {
1337         ArgParser parser;
1338
1339         // Initialize the ArgParser
1340         parser.itr = request;
1341         parser.buf = buffer_init( 128 );
1342
1343         int done = 0;               // boolean
1344         while( !done ) {
1345                 OSRF_BUFFER_RESET( parser.buf );
1346
1347                 // skip any white space or commas
1348                 while( *parser.itr
1349                            && ( isspace( (unsigned char) *parser.itr ) || ',' == *parser.itr ) )
1350                         ++parser.itr;
1351
1352                 if( '\0' == *parser.itr )
1353                         done = 1;
1354                 else if( '{' == *parser.itr ) {
1355                         // Load a JSON object
1356                         get_json_object( &parser );
1357                 } else if( '[' == *parser.itr ) {
1358                         // Load a JSON array
1359                         get_json_array( &parser );
1360                 } else if( '\"' == *parser.itr ) {
1361                         // Load a string literal
1362                         get_string_literal( &parser );
1363                 } else {
1364                         // Anything else is delimited by white space
1365                         do {
1366                                 OSRF_BUFFER_ADD_CHAR( parser.buf, *parser.itr );
1367                                 ++parser.itr;
1368                         } while( *parser.itr && ! isspace( (unsigned char) *parser.itr ) );
1369                 }
1370
1371                 // Remove a trailing comma, if present
1372                 char lastc = OSRF_BUFFER_C_STR( parser.buf )[
1373                         strlen( OSRF_BUFFER_C_STR( parser.buf )  ) - 1 ];
1374                 if( ',' == lastc )
1375                         buffer_chomp( parser.buf );
1376
1377                 // Add the chunk to the osrfStringArray
1378                 const char* s = OSRF_BUFFER_C_STR( parser.buf );
1379                 if( s && *s ) {
1380                         osrfStringArrayAdd( cmd_array, s );
1381                 }
1382         }
1383
1384         buffer_free( parser.buf );
1385 }
1386 /*@}*/
1387