1/*
2 This file is part of Mtproto-proxy Library.
3
4 Mtproto-proxy Library is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as published by
6 the Free Software Foundation, either version 2 of the License, or
7 (at your option) any later version.
8
9 Mtproto-proxy Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public License
15 along with Mtproto-proxy Library. If not, see <http://www.gnu.org/licenses/>.
16
17 Copyright 2013 Vkontakte Ltd
18 2013 Vitaliy Valtman
19 2013 Anton Maydell
20
21 Copyright 2014 Telegram Messenger Inc
22 2014 Vitaly Valtman
23 2014 Anton Maydell
24
25 Copyright 2015-2016 Telegram Messenger Inc
26 2015-2016 Vitaliy Valtman
27*/
28
29#define _FILE_OFFSET_BITS 64
30#define _GNU_SOURCE 1
31
32#include <errno.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <stdarg.h>
37#include <unistd.h>
38#include <signal.h>
39#include <assert.h>
40#include <sys/time.h>
41#include <sys/resource.h>
42#include <sys/wait.h>
43#include <time.h>
44#include <fcntl.h>
45
46#include "common/common-stats.h"
47#include "common/kprintf.h"
48#include "common/precise-time.h"
49#include "common/server-functions.h"
50#include "common/tl-parse.h"
51
52#include "engine/engine.h"
53#include "engine/engine-net.h"
54#include "engine/engine-rpc.h"
55#include "engine/engine-signals.h"
56
57#include "jobs/jobs.h"
58
59#include "net/net-connections.h"
60#include "net/net-crypto-aes.h"
61#include "net/net-msg-buffers.h"
62#include "net/net-thread.h"
63
64#include "vv/vv-io.h"
65
66
67#define DEFAULT_EPOLL_WAIT_TIMEOUT 37
68
69char *local_progname;
70
71double precise_now_diff;
72
73engine_t *engine_state;
74
75unsigned char server_ipv6[16];
76
77void default_cron (void) {
78 double new_precise_now_diff = get_utime_monotonic () - get_double_time ();
79 precise_now_diff = precise_now_diff * 0.99 + 0.01 * new_precise_now_diff;
80}
81
82static void default_nop (void) {}
83
84static int default_parse_option (int val) {
85 return -1;
86}
87
88/* {{{ SIGNAL ACTIONS */
89static void default_sighup (void) {
90}
91
92static void default_sigusr1 (void) {
93 reopen_logs_ext (engine_check_slave_mode_enabled ());
94}
95
96
97static void default_sigrtmax_9 (void) {
98}
99
100static void default_sigrtmax_8 (void) {
101}
102
103static void default_sigrtmax_4 (void) {
104}
105
106static void default_sigrtmax_1 (void) {
107}
108
109static void default_sigrtmax (void) {
110}
111/* }}} */
112
113void set_signals_handlers (void) /* {{{ */ {
114 ksignal (SIGINT, sigint_immediate_handler);
115 ksignal (SIGTERM, sigterm_immediate_handler);
116
117 set_debug_handlers ();
118}
119/* }}} */
120
121
122/* {{{ PIPE TO WAKEUP MAIN THREAD */
123static int pipe_read_end;
124static int pipe_write_end;
125
126void create_main_thread_pipe (void) {
127 int p[2];
128 if (pipe_read_end > 0) {
129 /* used in copyexec sending results child process */
130 vkprintf (2, "%s: closing #%d pipe read end file descriptor.\n", __func__, pipe_read_end);
131 close (pipe_read_end);
132 }
133 if (pipe_write_end > 0) {
134 vkprintf (2, "%s: closing #%d pipe write end file descriptor.\n", __func__, pipe_write_end);
135 close (pipe_write_end);
136 }
137 assert (pipe2 (p, O_NONBLOCK) >= 0);
138 pipe_read_end = p[0];
139 pipe_write_end = p[1];
140}
141
142void wakeup_main_thread (void) {
143 if (!pipe_write_end) { return; }
144 int x = 0;
145 int r = write (pipe_write_end, &x, 4);
146 if (r < 0) { assert (errno == EINTR || errno == EAGAIN); }
147}
148
149static int epoll_nop (int fd, void *data, event_t *ev) {
150 int x[100];
151 while (read (fd, x, 400) == 400) {}
152 return EVA_CONTINUE;
153}
154/* }}} */
155
156
157const char *get_version_string_override (void) __attribute__ ((weak));
158const char *get_version_string_override (void) {
159 return "unknown compiled at " __DATE__ " " __TIME__ " by gcc " __VERSION__;
160}
161
162const char *get_version_string (void) {
163 if (engine_state && engine_state->F && engine_state->F->FullVersionStr) {
164 return engine_state->F->FullVersionStr;
165 } else {
166 return get_version_string_override ();
167 }
168}
169
170void engine_set_epoll_wait_timeout (int epoll_wait_timeout) /* {{{ */ {
171 assert (1 <= epoll_wait_timeout && epoll_wait_timeout <= 1000);
172 engine_state->epoll_wait_timeout = epoll_wait_timeout;
173}
174/* }}} */
175
176static void raise_file_limit (int maxconn) /* {{{ */ {
177 const int gap = 16;
178 if (getuid ()) {
179 struct rlimit rlim;
180 if (getrlimit (RLIMIT_NOFILE, &rlim) < 0) {
181 kprintf ("%s: getrlimit (RLIMIT_NOFILE) fail. %m\n", __func__);
182 exit (1);
183 }
184 if (maxconn > rlim.rlim_cur - gap) {
185 maxconn = rlim.rlim_cur - gap;
186 }
187 tcp_set_max_connections (maxconn);
188 } else {
189 if (raise_file_rlimit (maxconn + gap) < 0) {
190 kprintf ("fatal: cannot raise open file limit to %d\n", maxconn + gap);
191 exit (1);
192 }
193 }
194}
195/* }}} */
196
197/* {{{ engine_init */
198
199void engine_init (const char *const pwd_filename, int do_not_open_port) {
200 engine_t *E = engine_state;
201
202 if (!do_not_open_port) {
203 engine_do_open_port ();
204 }
205
206 raise_file_limit (E->maxconn);
207
208 int aes_load_res = aes_load_pwd_file (pwd_filename);
209 if (aes_load_res < 0 && (aes_load_res != -0x80000000 || pwd_filename)) {
210 kprintf ("fatal: cannot load secret definition file `%s'\n", pwd_filename);
211 exit (1);
212 }
213
214 if (change_user_group (username, groupname) < 0) {
215 kprintf ("fatal: cannot change user to %s\n", username ? username : "(none)");
216 exit (1);
217 }
218
219
220
221 if (!do_not_open_port && E->port <= 0 && E->start_port <= E->end_port) {
222 E->port = try_open_port_range (E->start_port, E->end_port, 100, get_port_mod (), 1);
223 assert (E->port >= 0);
224 }
225
226 unsigned int ipv4 = 0;
227
228 if (E->settings_addr.s_addr) {
229 ipv4 = ntohl (E->settings_addr.s_addr);
230 if ((ipv4 >> 24) != 10) {
231 kprintf ("Bad binded IP address " IP_PRINT_STR ", search in ifconfig\n", IP_TO_PRINT (ipv4));
232 ipv4 = 0;
233 }
234 }
235 init_server_PID (ipv4 ? ipv4 : get_my_ipv4 (), E->port);
236 get_my_ipv6 (server_ipv6);
237 init_msg_buffers (0);
238
239 init_async_jobs ();
240
241 int nc;
242 nc = engine_get_required_io_threads ();
243 if (nc <= 0) {
244 nc = DEFAULT_IO_JOB_THREADS;
245 }
246 create_new_job_class (JC_IO, nc, nc);
247 nc = engine_get_required_cpu_threads ();
248 if (nc <= 0) {
249 nc = DEFAULT_CPU_JOB_THREADS;
250 }
251 create_new_job_class (JC_CPU, nc, nc);
252
253 if (engine_check_multithread_enabled ()) {
254 int nc;
255 nc = engine_get_required_tcp_cpu_threads ();
256 if (nc <= 0) {
257 nc = 1;
258 }
259 create_new_job_class (JC_CONNECTION, nc, nc);
260 nc = engine_get_required_tcp_io_threads ();
261 if (nc <= 0) {
262 nc = 1;
263 }
264 create_new_job_class (JC_CONNECTION_IO, nc, nc);
265 create_new_job_class (JC_ENGINE, 1, 1);
266 }
267
268 create_main_thread_pipe ();
269 alloc_timer_manager (JC_EPOLL);
270 notification_event_job_create ();
271
272 kprintf ("Started as " PID_PRINT_STR "\n", PID_TO_PRINT (&PID));
273}
274/* }}} */
275
276void server_init (conn_type_t *listen_connection_type, void *listen_connection_extra) /* {{{ */ {
277 engine_t *E = engine_state;
278 server_functions_t *F = E->F;
279 assert (F && "server functions aren't defined");
280
281 init_epoll ();
282
283 epoll_sethandler (pipe_read_end, 0, epoll_nop, NULL);
284 epoll_insert (pipe_read_end, EVT_READ | EVT_LEVEL);
285
286 if (daemonize) {
287 setsid ();
288 reopen_logs_ext (engine_check_slave_mode_enabled ());
289 }
290
291 if (!E->do_not_open_port) {
292 if (E->port <= 0) {
293 kprintf ("fatal: port isn't defined\n");
294 exit (1);
295 }
296 if (E->sfd <= 0) {
297 assert (try_open_port (E->port, 1) >= 0);
298 }
299
300 if (engine_check_tcp_enabled ()) {
301 if (!engine_check_ipv6_enabled ()) {
302 assert (init_listening_connection (E->sfd, listen_connection_type, listen_connection_extra) >= 0);
303 } else {
304 assert (init_listening_tcpv6_connection (E->sfd, listen_connection_type, listen_connection_extra, SM_IPV6) >= 0);
305 }
306 }
307
308 }
309
310 ksignal (SIGINT, sigint_handler);
311 ksignal (SIGTERM, sigterm_handler);
312 ksignal (SIGPIPE, empty_signal_handler);
313 ksignal (SIGPOLL, empty_signal_handler);
314
315 if (daemonize) {
316 ksignal (SIGHUP, default_signal_handler);
317 }
318}
319/* }}} */
320
321void server_exit (void) /* {{{ */ {
322 engine_t *E = engine_state;
323 server_functions_t *F = E->F;
324
325 F->close_net_sockets ();
326
327 if (signal_check_pending (SIGTERM)) {
328 kprintf ("Terminated by SIGTERM.\n");
329 } else if (signal_check_pending (SIGINT)) {
330 kprintf ("Terminated by SIGINT.\n");
331 }
332}
333/* }}} */
334
335/* {{{ precise cron */
336
337struct event_precise_cron precise_cron_events = {
338 .next = &precise_cron_events,
339 .prev = &precise_cron_events
340};
341
342void precise_cron_function_insert (struct event_precise_cron *ev) {
343 ev->next = &precise_cron_events;
344 ev->prev = precise_cron_events.prev;
345 ev->next->prev = ev->prev->next = ev;
346}
347
348void precise_cron_function_remove (struct event_precise_cron *ev) {
349 ev->next->prev = ev->prev;
350 ev->prev->next = ev->next;
351 ev->prev = ev->next = NULL;
352}
353
354static void do_precise_cron (void) {
355 engine_t *E = engine_state;
356 server_functions_t *F = E->F;
357 engine_process_signals ();
358
359 static int last_cron_time;
360 if (last_cron_time != now) {
361 last_cron_time = now;
362 F->cron ();
363 }
364
365 if (F->precise_cron) {
366 F->precise_cron ();
367 }
368
369 if (precise_cron_events.next != &precise_cron_events) {
370 struct event_precise_cron ev = precise_cron_events;
371 ev.next->prev = &ev;
372 ev.prev->next = &ev;
373 precise_cron_events.next = precise_cron_events.prev = &precise_cron_events;
374 while (ev.next != &ev) {
375 struct event_precise_cron *e = ev.next;
376 ev.next->wakeup (ev.next);
377 if (e == ev.next) {
378 precise_cron_function_remove (e);
379 precise_cron_function_insert (e);
380 }
381 }
382 }
383
384 free_later_act ();
385}
386/* }}} */
387
388double update_job_stats_gw (void *ex) {
389 update_all_thread_stats ();
390 return 10 + precise_now;
391}
392
393struct precise_cron_job_extra {
394 struct event_timer ev;
395};
396
397int precise_cron_job_run (job_t job, int op, struct job_thread *JT) /* {{{ */ {
398 if (op != JS_RUN && op != JS_ALARM) {
399 return JOB_ERROR;
400 }
401 if (op == JS_ALARM && !job_timer_check (job)) {
402 return 0;
403 }
404
405 do_precise_cron ();
406 job_timer_insert (job, precise_now + 0.001 * (1 + drand48_j ()));
407 return 0;
408}
409/* }}} */
410
411int terminate_job_run (job_t job, int op, struct job_thread *JT) {
412 if (op == JS_RUN) {
413 engine_t *E = engine_state;
414 server_functions_t *F = E->F;
415
416 if (F->on_exit) {
417 F->on_exit ();
418 }
419 server_exit ();
420 exit (0);
421 return 0;
422 }
423 return JOB_ERROR;
424}
425
426void default_engine_server_start (void) /* {{{ */ {
427 engine_t *E = engine_state;
428 server_functions_t *F = E->F;
429
430 engine_server_init ();
431
432 vkprintf (1, "Server started\n");
433
434 register_custom_op_cb (RPC_REQ_RESULT, engine_work_rpc_req_result);
435 if (F->custom_ops) {
436 struct rpc_custom_op *O = F->custom_ops;
437 while (O->op) {
438 register_custom_op_cb (O->op, O->func);
439 O ++;
440 }
441 }
442
443 job_t precise_cron_job = create_async_job (precise_cron_job_run, JSC_ALLOW (JC_ENGINE, JS_RUN) | JSC_ALLOW (JC_ENGINE, JS_ALARM) | JSC_ALLOW (JC_ENGINE, JS_FINISH), F->cron_subclass, sizeof (struct precise_cron_job_extra), JT_HAVE_TIMER, JOB_REF_NULL);
444 //struct precise_cron_job_extra *e = (void *)precise_cron_job->j_custom;
445 //memset (e, 0, sizeof (*e)); /* no need, create_async_job memsets itself */
446 precise_cron_job->j_refcnt ++;
447 schedule_job (JOB_REF_PASS (precise_cron_job));
448
449 job_t update_job_stats = job_timer_alloc (JC_MAIN, update_job_stats_gw, NULL);
450 job_timer_insert (update_job_stats, 1.0);
451
452 F->pre_loop ();
453
454 job_t terminate_job = create_async_job (terminate_job_run, JSC_ALLOW (JC_ENGINE, JS_RUN) | JSC_ALLOW (JC_ENGINE, JS_FINISH), -1, 0, 0, JOB_REF_NULL);
455 unlock_job (JOB_REF_CREATE_PASS (terminate_job));
456
457 int i;
458 vkprintf (0, "main loop\n");
459 for (i = 0; ; i++) {
460 epoll_work (engine_check_multithread_enabled () ? E->epoll_wait_timeout : 1);
461 if (interrupt_signal_raised ()) {
462 if (F->on_waiting_exit) {
463 while (1) {
464 useconds_t t = F->on_waiting_exit ();
465 if (t <= 0) {
466 break;
467 }
468 usleep (t);
469 run_pending_main_jobs ();
470 }
471 }
472 if (terminate_job) {
473 job_signal (JOB_REF_PASS (terminate_job), JS_RUN);
474 run_pending_main_jobs ();
475 }
476 break;
477 }
478
479 run_pending_main_jobs ();
480 }
481 sleep (120);
482 kprintf ("Did not exit after 120 seconds\n");
483 assert (0);
484}
485/* }}} */
486
487
488#define DATA_BUF_SIZE (1 << 20)
489static char data_buf[DATA_BUF_SIZE + 1];
490
491int engine_prepare_stats (void) {
492 if (!engine_state) { return 0; }
493 stats_buffer_t sb;
494 sb_init (&sb, data_buf, DATA_BUF_SIZE);
495 if (engine_state->F->prepare_stats) {
496 engine_state->F->prepare_stats (&sb);
497 }
498 return sb.pos;
499}
500
501void engine_rpc_stats (struct tl_out_state *tlio_out) {
502 engine_prepare_stats ();
503 tl_store_stats (tlio_out, data_buf, 0);
504}
505
506void output_engine_stats (void) {
507 int len = engine_prepare_stats ();
508 if (len > 0) {
509 kprintf ("-------------- network/memcache statistics ------------\n");
510 kwrite (2, data_buf, len);
511 }
512}
513
514int default_get_op (struct tl_in_state *tlio_in) {
515 return tl_fetch_lookup_int ();
516}
517
518void usage ();
519
520void check_signal_handler (server_functions_t *F, int sig, void (*default_f)(void)) {
521 if (F->allowed_signals & SIG2INT(sig)) {
522 if (!F->signal_handlers[sig]) {
523 F->signal_handlers[sig] = default_f;
524 }
525 }
526}
527
528unsigned long long default_signal_mask = SIG2INT(SIGHUP) | SIG2INT(SIGUSR1) | SIG2INT(OUR_SIGRTMAX) | SIG2INT(OUR_SIGRTMAX-1) | SIG2INT(OUR_SIGRTMAX-4) | SIG2INT(OUR_SIGRTMAX-8) | SIG2INT(OUR_SIGRTMAX-9);
529
530static void check_server_functions (void) /* {{{ */ {
531 engine_t *E = engine_state;
532 server_functions_t *F = E->F;
533 F->allowed_signals = (F->allowed_signals | default_signal_mask) & ~F->forbidden_signals;
534
535 check_signal_handler (F, SIGHUP, default_sighup);
536 check_signal_handler (F, SIGUSR1, default_sigusr1);
537 check_signal_handler (F, SIGRTMAX-9, default_sigrtmax_9);
538 check_signal_handler (F, SIGRTMAX-8, default_sigrtmax_8);
539 check_signal_handler (F, SIGRTMAX-4, default_sigrtmax_4);
540 check_signal_handler (F, SIGRTMAX-1, default_sigrtmax_1);
541 check_signal_handler (F, SIGRTMAX, default_sigrtmax);
542
543 if (!F->close_net_sockets) { F->close_net_sockets = default_close_network_sockets; }
544 if (!F->cron) { F->cron = default_cron; }
545 if (!F->parse_option) { F->parse_option = default_parse_option; }
546 if (!F->prepare_parse_options) { F->prepare_parse_options = default_nop; }
547 if (!F->pre_init) { F->pre_init = default_nop; }
548 if (!F->pre_start) { F->pre_start = default_nop; }
549 if (!F->parse_extra_args) { F->parse_extra_args = default_parse_extra_args; }
550 if (!F->pre_loop) { F->pre_loop = default_nop; }
551
552 if (!F->epoll_timeout) { F->epoll_timeout = 1; }
553 if (!F->aio_timeout) { F->aio_timeout = 0.5; }
554
555 if (!F->get_op) { F->get_op = default_get_op; }
556
557 int i;
558 for (i = 1; i <= 64; i++) {
559 if (F->allowed_signals & SIG2INT (i)) {
560 //fix log spamming hack for image-engine:
561 ksignal (i, i == SIGCHLD ? quiet_signal_handler : default_signal_handler);
562 }
563 }
564}
565/* }}} */
566
567void engine_startup (engine_t *E, server_functions_t *F) /* {{{ */ {
568 E->F = F;
569 E->modules = (ENGINE_DEFAULT_ENABLED_MODULES | F->default_modules) & ~F->default_modules_disabled;
570 engine_set_backlog (DEFAULT_BACKLOG);
571 tcp_set_default_rpc_flags (0xffffffff, RPCF_USE_CRC32C);
572 E->port = -1;
573
574 precise_now_diff = get_utime_monotonic () - get_double_time ();
575
576 assert (SIGRTMAX == OUR_SIGRTMAX);
577 assert (SIGRTMAX - SIGRTMIN >= 20);
578
579 E->sfd = 0;
580 E->epoll_wait_timeout = DEFAULT_EPOLL_WAIT_TIMEOUT;
581 E->maxconn = MAX_CONNECTIONS;
582
583 check_server_functions ();
584}
585/* }}} */
586
587int default_main (server_functions_t *F, int argc, char *argv[]) {
588 set_signals_handlers ();
589
590 engine_t *E = calloc (sizeof (*E), 1);
591 engine_state = E;
592
593 engine_startup (E, F);
594 engine_set_epoll_wait_timeout (F->epoll_timeout);
595
596 if (F->tcp_methods) {
597 engine_set_tcp_methods (F->tcp_methods);
598 }
599 if (F->http_functions) {
600 conn_type_t *H = F->http_type;
601 if (!H) {
602 H = &ct_http_server;
603 }
604 assert (check_conn_functions (H, 1) >= 0);
605 engine_set_http_fallback (H, F->http_functions);
606 }
607
608
609 kprintf ("Invoking engine %s\n", F->FullVersionStr);
610
611
612 progname = argv[0];
613 local_progname = argv[0];
614
615 add_builtin_parse_options ();
616
617 F->prepare_parse_options ();
618
619 parse_engine_options_long (argc, argv);
620
621 F->parse_extra_args (argc - optind, argv + optind);
622
623 E->do_not_open_port = (F->flags & ENGINE_NO_PORT);
624
625 F->pre_init ();
626
627 engine_init (engine_get_aes_pwd_file (), E->do_not_open_port);
628
629 vkprintf (3, "Command line parsed\n");
630
631 F->pre_start ();
632
633 start_time = time (NULL);
634
635 if (F->run_script) {
636 int r = F->run_script ();
637 if (r >= 0) {
638 return 0;
639 } else {
640 return -r;
641 }
642 }
643
644 engine_tl_init (F->parse_function, engine_rpc_stats, F->get_op, F->aio_timeout, F->ShortVersionStr);
645 init_epoll ();
646 default_engine_server_start ();
647
648 return 0;
649}
650
651
652static int f_parse_option_engine (int val) {
653 switch (val) {
654 case 227:
655 engine_set_required_cpu_threads (atoi (optarg));
656 break;
657 case 228:
658 engine_set_required_io_threads (atoi (optarg));
659 break;
660 case 258:
661 if (optarg && atoi (optarg) == 0) {
662 engine_disable_multithread ();
663 } else {
664 engine_enable_multithread ();
665 epoll_sleep_ns = 10000;
666 }
667 break;
668 case 301:
669 engine_set_required_tcp_cpu_threads (atoi (optarg));
670 break;
671 case 302:
672 engine_set_required_tcp_io_threads (atoi (optarg));
673 break;
674 default:
675 return -1;
676 }
677 return 0;
678}
679
680static void parse_option_engine_builtin (const char *name, int arg, int *var, int val, unsigned flags, const char *help, ...) __attribute__ ((format (printf, 6, 7)));
681static void parse_option_engine_builtin (const char *name, int arg, int *var, int val, unsigned flags, const char *help, ...) {
682 char *h;
683 va_list ap;
684 va_start (ap, help);
685 assert (vasprintf (&h, help, ap) >= 0);
686 va_end (ap);
687
688 parse_option_ex (name, arg, var, val, flags, f_parse_option_engine, h);
689
690 free (h);
691}
692
693void engine_add_engine_parse_options (void) {
694 parse_option_engine_builtin ("cpu-threads", required_argument, 0, 227, LONGOPT_JOBS_SET, "Number of CPU threads (1-64, default 8)");
695 parse_option_engine_builtin ("io-threads", required_argument, 0, 228, LONGOPT_JOBS_SET, "Number of I/O threads (1-64, default 16)");
696 parse_option_engine_builtin ("multithread", optional_argument, 0, 258, LONGOPT_JOBS_SET, "run in multithread mode");
697 parse_option_engine_builtin ("tcp-cpu-threads", required_argument, 0, 301, LONGOPT_JOBS_SET, "number of tcp-cpu threads");
698 parse_option_engine_builtin ("tcp-iothreads", required_argument, 0, 302, LONGOPT_JOBS_SET, "number of tcp-io threads");
699}
700
701void default_parse_extra_args (int argc, char *argv[]) /* {{{ */ {
702 if (argc != 0) {
703 vkprintf (0, "Extra args\n");
704 usage ();
705 }
706}
707/*}}}*/
708
709int default_parse_option_func (int a) {
710 if (engine_state) {
711 server_functions_t *F = engine_state->F;
712 if (F->parse_option) {
713 return F->parse_option (a);
714 } else {
715 return -1;
716 }
717 } else {
718 return -1;
719 }
720}
721
722