1 #include <cassert>
2 #include <cerrno>
3 #include <string>
4 #include <vector>
5 #include <list>
6 #include <cstring>
7 #include <cstdio>
8 #include <cstdlib>
9 #include <iostream>
10 #include <unistd.h>
11 #include <signal.h>
12 #include <readline/readline.h>
13 
14 #include "cmdline_parser.hpp"
15 #include "xmlrpc-c/girerr.hpp"
16 using girerr::throwf;
17 
18 #ifdef __cplusplus
19 extern "C" {
20 #endif
21 
22 #include "dumpvalue.h"  /* An internal Xmlrpc-c header file ! */
23 
24 #ifdef __cplusplus
25 }
26 #endif
27 
28 #include <xmlrpc-c/base.hpp>
29 #include <xmlrpc-c/client.hpp>
30 #include <xmlrpc-c/client_transport.hpp>
31 
32 using namespace std;
33 using namespace xmlrpc_c;
34 
35 /*----------------------------------------------------------------------------
36    Command line
37 -----------------------------------------------------------------------------*/
38 
39 class cmdlineInfo {
40 public:
41     int            serverfd;
42     bool           interactive;
43 
44     // Valid only if !interactive:
45     string         methodName;
46     vector<string> params;
47 
48     cmdlineInfo(int           const argc,
49                 const char ** const argv);
50 
51 private:
52     cmdlineInfo();
53 };
54 
55 
56 
57 static void
parseCommandLine(cmdlineInfo * const cmdlineP,int const argc,const char ** const argv)58 parseCommandLine(cmdlineInfo * const cmdlineP,
59                  int           const argc,
60                  const char ** const argv) {
61 
62     CmdlineParser cp;
63 
64     cp.defineOption("serverfd",       CmdlineParser::UINT);
65 
66     try {
67         cp.processOptions(argc, argv);
68     } catch (exception const& e) {
69         throwf("Command syntax error.  %s", e.what());
70     }
71 
72     if (cp.optionIsPresent("serverfd")) {
73         cmdlineP->serverfd = cp.getOptionValueUint("serverfd");
74     } else
75         cmdlineP->serverfd = 3;
76 
77     if (cp.argumentCount() < 1)
78         cmdlineP->interactive = true;
79     else {
80         cmdlineP->interactive = false;
81         cmdlineP->methodName = cp.getArgument(0);
82         for (uint argI = 1; argI < cp.argumentCount(); ++argI)
83             cmdlineP->params.push_back(cp.getArgument(argI));
84     }
85 }
86 
87 
88 
89 cmdlineInfo::
cmdlineInfo(int const argc,const char ** const argv)90 cmdlineInfo(int           const argc,
91             const char ** const argv) {
92 
93     try {
94         parseCommandLine(this, argc, argv);
95     } catch (exception const& e) {
96         throwf("Command syntax error.  %s", e.what());
97     }
98 }
99 
100 
101 
102 static value
bytestringValFromParm(string const & valueString)103 bytestringValFromParm(string const& valueString) {
104 
105     value retval;
106 
107     if (valueString.length() / 2 * 2 != valueString.length())
108         throwf("Hexadecimal text is not an even "
109                "number of characters (it is %u characters)",
110                (unsigned)valueString.length());
111     else {
112         vector<unsigned char> byteString(valueString.length() / 2);
113         size_t strCursor;
114 
115         strCursor = 0;
116 
117         while (strCursor < valueString.length()) {
118             string const hexByte(valueString.substr(strCursor, 2));
119 
120             unsigned char byte;
121             int rc;
122 
123             rc = sscanf(hexByte.c_str(), "%2hhx", &byte);
124 
125             byteString.push_back(byte);
126 
127             if (rc != 1)
128                 throwf("Invalid hex data '%s'", hexByte.c_str());
129             else
130                 strCursor += 2;
131         }
132         retval = value_bytestring(byteString);
133     }
134     return retval;
135 }
136 
137 
138 
139 static value
intValFromParm(string const & valueString)140 intValFromParm(string const& valueString) {
141 
142     value retval;
143 
144     if (valueString.length() < 1)
145         throwf("Integer argument has nothing after the 'i/'");
146     else {
147         long longValue;
148         char * tailptr;
149 
150         errno = 0;
151 
152         longValue = strtol(valueString.c_str(), &tailptr, 10);
153 
154         if (errno == ERANGE)
155             throwf("'%s' is out of range for a 32 bit integer",
156                    valueString.c_str());
157         else if (errno != 0)
158             throwf("Mysterious failure of strtol(), errno=%d (%s)",
159                    errno, strerror(errno));
160         else {
161             if (*tailptr != '\0')
162                 throwf("Integer argument has non-digit crap in it: '%s'",
163                        tailptr);
164             else
165                 retval = value_int(longValue);
166         }
167     }
168     return retval;
169 }
170 
171 
172 
173 static value
boolValFromParm(string const & valueString)174 boolValFromParm(string const& valueString) {
175 
176     value retval;
177 
178     if (valueString == "t" || valueString == "true")
179         retval = value_boolean(true);
180     else if (valueString == "f" || valueString == "false")
181         retval = value_boolean(false);
182     else
183         throwf("Boolean argument has unrecognized value '%s'.  "
184                "recognized values are 't', 'f', 'true', and 'false'.",
185                valueString.c_str());
186 
187     return retval;
188 }
189 
190 
191 
192 static value
doubleValFromParm(string const & valueString)193 doubleValFromParm(string const& valueString) {
194 
195     value retval;
196 
197     if (valueString.length() < 1)
198         throwf("\"Double\" argument has nothing after the 'd/'");
199     else {
200         double value;
201         char * tailptr;
202 
203         value = strtod(valueString.c_str(), &tailptr);
204 
205         if (*tailptr != '\0')
206             throwf("\"Double\" argument has non-decimal crap in it: '%s'",
207                    tailptr);
208         else
209             retval = value_double(value);
210     }
211     return retval;
212 }
213 
214 
215 
216 static value
nilValFromParm(string const & valueString)217 nilValFromParm(string const& valueString) {
218 
219     value retval;
220 
221     if (valueString.length() > 0)
222         throwf("Nil argument has something after the 'n/'");
223     else
224         retval = value_nil();
225 
226     return retval;
227 }
228 
229 
230 
231 static value
i8ValFromParm(string const & valueString)232 i8ValFromParm(string  const& valueString) {
233 
234     value retval;
235 
236     if (valueString.length() < 1)
237         throwf("Integer argument has nothing after the 'I/'");
238     else {
239         long long value;
240         char * tailptr;
241 
242         errno = 0;
243 
244         value = strtoll(valueString.c_str(), &tailptr, 10);
245 
246         if (errno == ERANGE)
247             throwf("'%s' is out of range for a 64 bit integer",
248                    valueString.c_str());
249         else if (errno != 0)
250             throwf("Mysterious failure of strtoll(), errno=%d (%s)",
251                    errno, strerror(errno));
252         else {
253             if (*tailptr != '\0')
254                 throwf("64 bit integer argument has non-digit crap "
255                        "in it: '%s'",
256                        tailptr);
257             else
258                 retval = value_i8(value);
259         }
260     }
261     return retval;
262 }
263 
264 
265 
266 static value
parameterFromArg(string const & paramArg)267 parameterFromArg(string  const& paramArg) {
268 
269     value param;
270 
271     try {
272         if (paramArg.substr(0, 2) == "s/")
273             param = value_string(paramArg.substr(2));
274         else if (paramArg.substr(0, 2) == "h/")
275             param = bytestringValFromParm(paramArg.substr(2));
276         else if (paramArg.substr(0, 2) == "i/")
277             param = intValFromParm(paramArg.substr(2));
278         else if (paramArg.substr(0, 2) == "I/")
279             param = i8ValFromParm(paramArg.substr(2));
280         else if (paramArg.substr(0, 2) == "d/")
281             param = doubleValFromParm(paramArg.substr(2));
282         else if (paramArg.substr(0, 2) == "b/")
283             param = boolValFromParm(paramArg.substr(2));
284         else if (paramArg.substr(0, 2) == "n/")
285             param = nilValFromParm(paramArg.substr(2));
286         else {
287             /* It's not in normal type/value format, so we take it to be
288                the shortcut string notation
289             */
290             param = value_string(paramArg);
291         }
292     } catch (exception const& e) {
293         throwf("Failed to interpret parameter argument '%s'.  %s",
294                paramArg.c_str(), e.what());
295     }
296     return param;
297 }
298 
299 
300 
301 static paramList
paramListFromParamArgs(vector<string> const & params)302 paramListFromParamArgs(vector<string> const& params) {
303 
304     paramList paramList;
305 
306     for (vector<string>::const_iterator p = params.begin();
307          p != params.end(); ++p)
308         paramList.add(parameterFromArg(*p));
309 
310     return paramList;
311 }
312 
313 
314 
315 static void
callWithClient(client * const clientP,string const & methodName,paramList const & paramList,value * const resultP)316 callWithClient(client *  const  clientP,
317                string    const& methodName,
318                paramList const& paramList,
319                value *   const  resultP) {
320 
321     rpcPtr myRpcP(methodName, paramList);
322 
323     carriageParm_pstream myCarriageParm;  // Empty - no parm needed
324 
325     try {
326         myRpcP->call(clientP, &myCarriageParm);
327     } catch (exception const& e) {
328         throwf("RPC failed.  %s", e.what());
329     }
330     *resultP = myRpcP->getResult();
331 }
332 
333 
334 
335 static void
dumpResult(value const & result)336 dumpResult(value const& result) {
337 
338     cout << "Result:" << endl << endl;
339 
340     /* Here we borrow code from inside Xmlrpc-c, and also use an
341        internal interface of xmlrpc_c::value.  This sliminess is one
342        reason that this is Bryan's private code instead of part of the
343        Xmlrpc-c package.
344 
345        Note that you must link with the dumpvalue.o object module from
346        inside an Xmlrpc-c build tree.
347     */
348 
349     dumpValue("", result.cValueP);
350 }
351 
352 
353 
354 static list<string>
parseWordList(string const & wordString)355 parseWordList(string const& wordString) {
356 
357     list<string> retval;
358 
359     unsigned int pos;
360 
361     pos = 0;
362 
363     while (pos < wordString.length()) {
364         pos = wordString.find_first_not_of(' ', pos);
365 
366         if (pos < wordString.length()) {
367             unsigned int const end = wordString.find_first_of(' ', pos);
368 
369             retval.push_back(wordString.substr(pos, end - pos));
370 
371             pos = end;
372         }
373     }
374     return retval;
375 }
376 
377 
378 
379 static void
parseCommand(string const & cmd,string * const methodNameP,vector<string> * const paramListP)380 parseCommand(string           const& cmd,
381              string *         const  methodNameP,
382              vector<string> * const  paramListP) {
383 
384     list<string> const wordList(parseWordList(cmd));
385 
386     list<string>::const_iterator cmdWordP;
387 
388     cmdWordP = wordList.begin();
389 
390     if (cmdWordP == wordList.end())
391         throwf("Command '%s' does not have a method name", cmd.c_str());
392     else {
393         *methodNameP = *cmdWordP++;
394 
395         *paramListP = vector<string>();  // Start empty
396 
397         while (cmdWordP != wordList.end())
398             paramListP->push_back(*cmdWordP++);
399     }
400 }
401 
402 
403 
404 static void
doCommand(client_xml * const clientP,string const & methodName,vector<string> const & paramArgs)405 doCommand(client_xml *   const  clientP,
406           string         const& methodName,
407           vector<string> const& paramArgs) {
408 
409     value result;
410 
411     callWithClient(clientP, methodName, paramListFromParamArgs(paramArgs),
412                    &result);
413 
414     try {
415         dumpResult(result);
416     } catch(exception const& e) {
417         throwf("Error showing result after RPC completed normally.  %s",
418                e.what());
419     }
420 }
421 
422 
423 
424 static void
getCommand(string * const cmdP,bool * const eofP)425 getCommand(string * const cmdP,
426            bool*    const eofP) {
427 
428     const char * cmd;
429 
430     cmd = readline(">");
431 
432     *eofP = (cmd == NULL);
433 
434     if (cmd != NULL) {
435         *cmdP = string(cmd);
436 
437         free(const_cast<char *>(cmd));
438     }
439 }
440 
441 
442 
443 static void
doInteractive(client_xml * const clientP)444 doInteractive(client_xml * const clientP) {
445 
446     bool quitRequested;
447 
448     quitRequested = false;
449 
450     while (!quitRequested) {
451         string cmd;
452         bool eof;
453 
454         getCommand(&cmd, &eof);
455 
456         if (eof) {
457             quitRequested = true;
458             cout << endl;
459         } else {
460             try {
461                 string methodName;
462                 vector<string> paramArgs;
463 
464                 parseCommand(cmd, &methodName, &paramArgs);
465 
466                 doCommand(clientP, methodName, paramArgs);
467             } catch (exception const& e) {
468                 cout << "Command failed.  " << e.what() << endl;
469             }
470         }
471     }
472 }
473 
474 
475 
476 int
main(int const argc,const char ** const argv)477 main(int           const argc,
478      const char ** const argv) {
479 
480     try {
481         cmdlineInfo cmdline(argc, argv);
482 
483         signal(SIGPIPE, SIG_IGN);
484 
485         clientXmlTransport_pstream myTransport(
486             clientXmlTransport_pstream::constrOpt()
487             .fd(cmdline.serverfd));
488 
489         client_xml myClient(&myTransport);
490 
491         if (cmdline.interactive) {
492             if (cmdline.serverfd == STDIN_FILENO ||
493                 cmdline.serverfd == STDOUT_FILENO)
494                 throwf("Can't use Stdin or Stdout for the server fd when "
495                        "running interactively.");
496             doInteractive(&myClient);
497         } else
498             doCommand(&myClient, cmdline.methodName, cmdline.params);
499 
500     } catch (exception const& e) {
501         cerr << "Failed.  " << e.what() << endl;
502     } catch (...) {
503         cerr << "Code threw unrecognized exception" << endl;
504         abort();
505     }
506     return 0;
507 }
508