]> git.mxchange.org Git - flightgear.git/blob - utils/fgcom/fgcom_init.cxx
fgadmin: add C string header
[flightgear.git] / utils / fgcom / fgcom_init.cxx
1 //
2 // fgcom_init.cxx -- FGCOM configuration parsing and initialization
3 // FGCOM: Copyright (C) H. Wirtz <wirtz@dfn.de>
4 //
5 // Adaption of fg_init.cxx from FlightGear
6 // FlightGear: Copyright (C) 1997  Curtis L. Olson  - http://www.flightgear.org/~curt
7 //
8 // Huge part rewritten by Tobias Ramforth to fit needs of FGCOM.
9 // <tobias@ramforth.com>
10 //
11 //
12 // This program is free software; you can redistribute it and/or
13 // modify it under the terms of the GNU General Public License as
14 // published by the Free Software Foundation; either version 2 of the
15 // License, or (at your option) any later version.
16 //
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 // General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
25 //
26
27 #ifdef HAVE_CONFIG_H
28 #  include <config.h>
29 #endif
30
31 #if defined( _MSC_VER) || defined(__MINGW32__)
32 #  include <direct.h>           // for getcwd()
33 #  define getcwd _getcwd
34 #endif
35
36 #ifdef _MSC_VER
37 #include "fgcom_getopt.h"
38 #else
39 #include <pwd.h>
40 #endif
41
42 #include <iostream>
43 #include <fstream>
44 #include <iomanip>
45 #include <map>
46
47 #include "fgcom_init.hxx"
48 #include "fgcom.hxx"
49 #include "utils.hxx"
50
51 #include <simgear/debug/logstream.hxx>
52
53 using namespace std;
54 using std::string;
55 using std::cerr;
56 using std::endl;
57
58 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
59
60 // Manipulators
61 istream& skip_eol( istream& in ) {
62     char c = '\0';
63     // skip to end of line.
64     while ( in.get(c) ) {
65         if ( (c == '\n') || (c == '\r') ) {
66             break;
67         }
68     }
69     return in;
70 }
71
72 istream& skip_ws( istream& in ) {
73     char c;
74     while ( in.get(c) ) {
75         if ( ! isspace( c ) ) {
76             // put back the non-space character
77             in.putback(c);
78             break;
79         }
80     }
81     return in;
82 }
83
84 istream& skip_comment( istream& in )
85 {
86     while ( in ) {
87         // skip whitespace
88         in >> skip_ws;
89         char c;
90         if ( in.get( c ) && c != '#' ) {
91             // not a comment
92             in.putback(c);
93             break;
94         }
95         in >> skip_eol;
96     }
97     return in;
98 }
99
100
101 /* program name */
102 extern char *prog;
103 extern const char * default_root;
104
105 static std::string config;
106
107 static OptionEntry *fgcomOptionArray = 0;
108
109 static void _doOptions (int argc, char **argv);
110 static int _parseOption (const std::string & arg, const std::string & next_arg);
111 static void _fgcomParseArgs (int argc, char **argv);
112 static void _fgcomParseOptions (const std::string & path);
113
114 // Read in configuration (file and command line)
115 bool fgcomInitOptions (const OptionEntry * fgcomOptions, int argc, char **argv)
116 {
117     if (!fgcomOptions) {
118         SG_LOG( SG_GENERAL, SG_ALERT, "Error! Uninitialized fgcomOptionArray!" );
119         return false;
120     }
121
122     // set option array
123     int n_options;
124     for (n_options = 0; fgcomOptions[n_options].long_option != NULL; n_options++) {}
125
126     fgcomOptionArray = (OptionEntry *) realloc (fgcomOptionArray, sizeof (OptionEntry) * (n_options + 1));
127     memcpy (fgcomOptionArray, fgcomOptions, sizeof (OptionEntry) * (n_options + 1));
128
129     // parse options
130     _doOptions (argc, argv);
131
132     return true;
133 }
134
135 // Create usage information out of fgcomOptionArray
136 void
137 fgcomUsage ()
138 {
139   size_t
140     max_length = 0;
141
142   if (!fgcomOptionArray)
143     {
144       SG_LOG( SG_GENERAL, SG_ALERT, "Error! Options need to be initialized by calling 'fgcomInitConfig'!" );
145       return;
146     }
147
148
149   // find longest long_option
150   const OptionEntry *
151     currentEntry = &fgcomOptionArray[0];
152   while (currentEntry->long_option != 0)
153     {
154       size_t
155         current_length = strlen (currentEntry->long_option);
156       if (current_length > max_length)
157         {
158           max_length = current_length;
159         }
160       currentEntry++;
161     }
162
163   max_length *= 2;
164   max_length += 10;             // for "-o, --option=, -option"
165
166   // print head
167   std::cout << "  OPTION" << std::string (max_length - 8,
168                                           ' ') << "\t\t" << "DESCRIPTION" <<
169     std::endl;
170   std::cout << std::endl;
171
172   // iterate through option array
173   currentEntry = &fgcomOptionArray[0];
174   while (currentEntry->long_option != 0)
175     {
176       size_t
177         current_length = strlen (currentEntry->long_option);
178
179       current_length *= 2;
180       current_length += 10;
181
182       std::string option = std::string ("  -")
183         + std::string (&currentEntry->option);
184       if (option.size() > 4)
185           option = option.substr(0,4);
186         option += std::string (", -")
187         + std::string (currentEntry->long_option)
188         + std::string (", --")
189         + std::string (currentEntry->long_option)
190         + std::string ("=") + std::string (max_length - current_length, ' ');
191
192       std::cout << option << "\t\t" << currentEntry->description;
193
194       if (currentEntry->has_param && (currentEntry->type != OPTION_NONE)
195           && (currentEntry->default_value != 0))
196         {
197           std::cout << " (default: '";
198
199           if (currentEntry->type == OPTION_NONE)
200             {
201
202             }
203           else if (currentEntry->type == OPTION_BOOL)
204             {
205               std::cout << *(bool *) currentEntry->default_value;
206             }
207           else if (currentEntry->type == OPTION_STRING)
208             {
209               std::cout << (char *) currentEntry->default_value;
210             }
211           else if (currentEntry->type == OPTION_FLOAT)
212             {
213               std::cout << *(float *) currentEntry->default_value;
214             }
215           else if (currentEntry->type == OPTION_DOUBLE)
216             {
217               std::cout << *(double *) currentEntry->default_value;
218             }
219           else if (currentEntry->type == OPTION_FREQ)
220             {
221               std::cout << std::setw (7) << std::
222                 setprecision (3) << *(double *) currentEntry->default_value;
223             }
224           else if (currentEntry->type == OPTION_INT)
225             {
226               std::cout << *(int *) currentEntry->default_value;
227             }
228           else if (currentEntry->type == OPTION_CHAR)
229             {
230               std::cout << *(char *) currentEntry->default_value;
231             }
232
233           std::cout << "')";
234         }
235
236       std::cout << std::endl;
237
238       currentEntry++;
239     }
240
241   std::cout << std::endl;
242
243   std::cout << "  Available codecs:" << std::endl;
244   std::cout << "  \t" <<
245     "u - ulaw (default and best codec because the mixing is based onto ulaw)"
246     << std::endl;
247   std::cout << "  \t" << "a - alaw" << std::endl;
248   std::cout << "  \t" << "g - gsm" << std::endl;
249   std::cout << "  \t" << "s - speex" << std::endl;
250   std::cout << "  \t" << "7 - G.723" << std::endl;
251
252   std::cout << std::endl;
253   std::cout << std::endl;
254
255   std::cout << "  Mode 1: client for COM1 of flightgear:" << std::endl;
256   std::cout << "  \t" << "$ " << prog << std::endl;
257   std::cout << "  - connects " << prog << " to fgfs at localhost:" <<
258     DEFAULT_FG_PORT << std::endl;
259   std::cout << "  \t" << "$ " << prog << " -sother.host.tld -p23456" <<
260     std::endl;
261   std::cout << "  - connects " << prog << " to fgfs at other.host.tld:23456" << std::endl;
262
263   std::cout << std::endl;
264
265   std::cout << "  Mode 2: client for an ATC at <airport> on <frequency>:" <<
266     std::endl;
267   std::cout << "  \t" << "$ " << prog << " -aKSFO -f120.500" << std::endl;
268   std::cout << "  - sets up " << prog <<
269     " for an ATC radio at KSFO 120.500 MHz" << std::endl;
270
271   std::cout << std::endl;
272   std::cout << std::endl;
273
274   std::cout << "Note that " << prog <<
275     " starts with a guest account unless you use -U and -P!" << std::endl;
276
277   std::cout << std::endl;
278 }
279
280 static char *
281 get_alternate_home(void)
282 {
283         char *ah = 0;
284 #ifdef _MSC_VER
285         char *app_data = getenv("LOCALAPPDATA");
286         if (app_data) {
287                 ah  = _strdup(app_data);
288         }
289 #else
290       struct passwd *
291         pwd = getpwent ();
292       ah = strdup (pwd->pw_dir);
293 #endif
294         return ah;
295 }
296
297 // Attempt to locate and parse the various non-XML config files in order
298 // from least precidence to greatest precidence
299 static void
300 _doOptions (int argc, char **argv)
301 {
302   char *
303     homedir = getenv ("HOME");
304
305   if (homedir == NULL)
306     {
307           homedir = get_alternate_home();
308     }
309
310   // Check for ~/.fgfsrc
311   if (homedir != NULL)
312     {
313       config = string (homedir);
314
315 #ifdef _WIN32
316       config.append ("\\");
317 #else
318       config.append ("/");
319 #endif
320
321       config.append (".fgcomrc");
322       _fgcomParseOptions (config);
323     }
324
325   // Parse remaining command line options
326   // These will override anything specified in a config file
327   _fgcomParseArgs (argc, argv);
328 }
329
330 // lookup maps
331 static std::map<string, string> fgcomOptionMap;
332 static std::map<string, size_t> fgcomLongOptionMap;
333
334 // Parse a single option
335 static int
336 _parseOption (const std::string & arg, const std::string & next_arg)
337 {
338   if (fgcomLongOptionMap.size () == 0)
339     {
340       size_t i = 0;
341       const OptionEntry * entry = &fgcomOptionArray[0];
342       while (entry->long_option != 0)
343         {
344           fgcomLongOptionMap.insert (std::pair < std::string,
345                                      size_t >
346                                      (std::string (entry->long_option), i));
347           fgcomOptionMap.insert (std::pair < std::string,
348                                  std::string >
349                                  (std::string (1, entry->option),
350                                   std::string (entry->long_option)));
351           i += 1;
352           entry += 1;
353         }
354     }
355
356   // General Options
357   if ((arg == "--help") || (arg == "-h") || (arg == "-?"))
358     {
359       // help/usage request
360       return FGCOM_OPTIONS_HELP;
361     }
362   else if ((arg == "--verbose") || (arg == "-v"))
363     {
364       // verbose help/usage request
365       return FGCOM_OPTIONS_VERBOSE_HELP;
366     }
367   else
368     {
369       std::map < string, size_t >::iterator it;
370       std::string arg_name, arg_value;
371
372       if (arg.find ("--") == 0)
373         {
374           size_t
375             pos = arg.find ('=');
376           if (pos == string::npos)
377             {
378               // did not find a value
379               arg_name = arg.substr (2);
380
381               // now there are two possibilities:
382               //              1: this is an option without a value
383               //              2: the value can be found in next_arg
384               if (next_arg.empty ())
385                 {
386                   // ok, value cannot be in next_arg
387                   SG_LOG( SG_GENERAL, SG_DEBUG, "option '" << arg_name << "'" );
388                 }
389               else
390                 {
391                   if (next_arg.at (0) == '-')
392                     {
393                       // there is no value, new option starts in next_arg
394                       SG_LOG( SG_GENERAL, SG_DEBUG, "option '" << arg_name << "'" );
395                     }
396                   else
397                     {
398                       // the value is in next_arg
399                       arg_value = std::string (next_arg);
400                       SG_LOG( SG_GENERAL, SG_DEBUG, "option '" << arg_name << "' with argument '" << arg_value << "'" );
401                     }
402                 }
403             }
404           else
405             {
406               // found a value
407               arg_name = arg.substr (2, pos - 2);
408               arg_value = arg.substr (pos + 1);
409               SG_LOG( SG_GENERAL, SG_DEBUG, "option '" << arg_name << "' with argument '" << arg_value << "'" );
410             }
411
412           it = fgcomLongOptionMap.find (arg_name);
413         }
414       else
415         {
416           std::map < string, string >::iterator it_b;
417           arg_name = arg.substr (1, 1);
418           arg_value = arg.substr (2);
419
420           if (arg_name.empty ())
421             {
422               SG_LOG (SG_GENERAL, SG_ALERT, "Unknown option '" << arg << "'");
423               return FGCOM_OPTIONS_ERROR;
424             }
425
426           SG_LOG( SG_GENERAL, SG_DEBUG, "option '" << arg_name << "' with argument '" << arg_value << "'" );
427
428           it_b = fgcomOptionMap.find (arg_name);
429
430           if (it_b != fgcomOptionMap.end ())
431             {
432               it = fgcomLongOptionMap.find (it_b->second);
433             }
434           else
435             {
436               SG_LOG( SG_GENERAL, SG_ALERT, "Unknown option '" << arg << "'" );
437               return FGCOM_OPTIONS_ERROR;
438             }
439         }
440
441       if (it != fgcomLongOptionMap.end ())
442         {
443           const OptionEntry *
444             entry = &fgcomOptionArray[it->second];
445           switch (entry->type)
446             {
447             case OPTION_BOOL:
448               *(bool *) entry->parameter = true;
449               break;
450             case OPTION_STRING:
451               if (entry->has_param && !arg_value.empty ())
452                 {
453                   *(char **) entry->parameter = strdup (arg_value.c_str ());
454                 }
455               else if (entry->has_param)
456                 {
457                   SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' needs a parameter" );
458                   return FGCOM_OPTIONS_ERROR;
459                 }
460               else
461                 {
462                   SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' does not have a parameter" );
463                   return FGCOM_OPTIONS_ERROR;
464                 }
465               break;
466             case OPTION_FLOAT:
467               if (!arg_value.empty ())
468                 {
469 #ifdef _MSC_VER
470                   float temp = atof(arg_value.c_str ());
471 #else // !_MSC_VER
472                   char *
473                     end;
474                   float
475                     temp = strtof (arg_value.c_str (), &end);
476
477                   errno = 0;
478
479                   if (*end != '\0')
480                     {
481                       SG_LOG( SG_GENERAL, SG_ALERT, "Cannot parse float value '" << arg_value << "' for option " << arg_name << "!" );
482                       return FGCOM_OPTIONS_ERROR;
483                     }
484 #endif // _MSC_VER y/n
485
486                   *(float *) (entry->parameter) = temp;
487                   if (*(float *) (entry->parameter) != temp
488                       || errno == ERANGE)
489                     {
490                       SG_LOG( SG_GENERAL, SG_ALERT, "Float value '" << arg_value << "' for option " << arg_name << " out of range!" );
491                       return FGCOM_OPTIONS_ERROR;
492                     }
493                 }
494               else
495                 {
496                   SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' needs a parameter" );
497                   return FGCOM_OPTIONS_ERROR;
498                 }
499               break;
500             case OPTION_DOUBLE:
501         case OPTION_FREQ:
502               if (!arg_value.empty ())
503                 {
504                   char *
505                     end;
506                   double
507                     temp = strtod (arg_value.c_str (), &end);
508
509                   errno = 0;
510
511                   if (*end != '\0')
512                     {
513                       SG_LOG( SG_GENERAL, SG_ALERT, "Cannot parse double value '" << arg_value << "' for option " << arg_name << "!" );
514                       return FGCOM_OPTIONS_ERROR;
515                     }
516
517                   *(double *) (entry->parameter) = temp;
518                   if (*(double *) (entry->parameter) != temp
519                       || errno == ERANGE)
520                     {
521                       SG_LOG( SG_GENERAL, SG_ALERT, "Double value '" << arg_value << "' for option " << arg_name << " out of range!" );
522                       return FGCOM_OPTIONS_ERROR;
523                     }
524                 }
525               else
526                 {
527                   SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' needs a parameter" );
528                   return FGCOM_OPTIONS_ERROR;
529                 }
530               break;
531             case OPTION_INT:
532               if (!arg_value.empty ())
533                 {
534                   char *
535                     end;
536                   long
537                     temp = strtol (arg_value.c_str (), &end, 0);
538
539                   errno = 0;
540
541                   if (*end != '\0')
542                     {
543                       SG_LOG( SG_GENERAL, SG_ALERT, "Cannot parse integer value '" << arg_value << "' for option " << arg_name << "!" );
544                       return FGCOM_OPTIONS_ERROR;
545                     }
546
547                   *(int *) (entry->parameter) = temp;
548                   if (*(int *) (entry->parameter) != temp || errno == ERANGE)
549                     {
550                       SG_LOG( SG_GENERAL, SG_ALERT, "Integer value '" << arg_value << "' for option " << arg_name << " out of range!" );
551                       return FGCOM_OPTIONS_ERROR;
552                     }
553                 }
554               else
555                 {
556                   SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' needs a parameter" );
557                   return FGCOM_OPTIONS_ERROR;
558                 }
559               break;
560             case OPTION_CHAR:
561               if (entry->has_param && !arg_value.empty ())
562                 {
563                   if (arg_value.length () > 1)
564                     {
565                       SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' needs a single char as parameter" );
566                       return FGCOM_OPTIONS_ERROR;
567                     }
568                   else
569                     {
570                       *(char *) entry->parameter = arg_value.c_str ()[0];
571                     }
572                 }
573               else if (entry->has_param)
574                 {
575                   SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' needs a parameter" );
576                   return FGCOM_OPTIONS_ERROR;
577                 }
578               else
579                 {
580                   SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' does not have a parameter" );
581                   return FGCOM_OPTIONS_ERROR;
582                 }
583               break;
584             case OPTION_NONE:
585               *(bool *) entry->parameter = true;
586               break;
587             }
588         }
589       else
590         {
591           SG_LOG( SG_GENERAL, SG_ALERT, "Unknown option '" << arg << "'" );
592           return FGCOM_OPTIONS_ERROR;
593         }
594     }
595
596   return FGCOM_OPTIONS_OK;
597 }
598
599 // Parse the command line options
600 static void
601 _fgcomParseArgs (int argc, char **argv)
602 {
603   SG_LOG( SG_GENERAL, SG_DEBUG, "Processing commandline options" );
604
605   for (int i = 1; i < argc; i++)
606     {
607       std::string arg = std::string (argv[i]);
608       std::string next_arg;
609       if (i < argc - 1)
610         {
611           next_arg = std::string (argv[i + 1]);
612         }
613
614       if (arg.find ('-') == 0)
615         {
616           if (arg == "--")
617             {
618               // do nothing
619             }
620           else
621             {
622               int
623                 result = _parseOption (arg, next_arg);
624
625               if (result == FGCOM_OPTIONS_OK)
626                 {
627                   // that is great
628                 }
629               else if (result == FGCOM_OPTIONS_HELP)
630                 {
631                   fgcomUsage ();
632                   exit (0);
633                 }
634               else
635                 {
636                   SG_LOG( SG_GENERAL, SG_ALERT, "Error parsing commandline options !" );
637                   exit (1);
638                 }
639             }
640         }
641     }
642   SG_LOG( SG_GENERAL, SG_ALERT, "Successfully parsed commandline options" );
643 }
644
645 // Parse config file options
646 static void
647 _fgcomParseOptions (const std::string & path)
648 {
649     if (is_file_or_directory(path.c_str()) != 1) {
650         SG_LOG( SG_GENERAL, SG_DEBUG, "Error: Unable to open " << path );
651         return;
652     }
653
654     std::fstream in;
655     std::ios_base::openmode mode = std::ios_base::in;
656     in.open(path.c_str(),mode);
657     if (!in.is_open ()) {
658         SG_LOG( SG_GENERAL, SG_DEBUG, "Error: DEBUG: Unable to open " << path );
659         return;
660     }
661
662     SG_LOG(SG_GENERAL, SG_DEBUG, "Processing config file: " << path );
663
664     in >> skip_comment;
665     while (!in.eof ()) {
666         std::string line;
667         getline (in, line, '\n');
668
669         // catch extraneous (DOS) line ending character
670         int i;
671         for (i = line.length(); i > 0; i--) {
672             if (line[i - 1] > 32) {
673                 break;
674             }
675         }
676
677         line = line.substr(0, i);
678
679         std::string next_arg;
680         if (_parseOption (line, next_arg) == FGCOM_OPTIONS_ERROR) {
681             SG_LOG( SG_GENERAL, SG_ALERT, "ERROR: Config file parse error: " << path << " '" << line << "'" );
682             fgcomUsage ();
683             exit(1);
684         }
685
686         in >> skip_comment;
687     }
688 }
689
690 /* eof - fgcom_init.cpp */