1 /*
2 This file is part of GNU APL, a free implementation of the
3 ISO/IEC Standard 13751, "Programming Language APL, Extended"
4
5 Copyright (C) 2008-2015 Dr. Jürgen Sauermann
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <errno.h>
22 #include <fcntl.h> /* For O_* constants */
23 #include <limits.h>
24 #include <netinet/tcp.h>
25 #include <signal.h>
26 #include <string.h>
27 #include <stdio.h>
28 #include <sys/time.h>
29
30 #include "config.h" // for HAVE_ macros
31
32 #ifdef HAVE_SYS_UN_H
33 #include <sys/un.h>
34 #endif
35
36 #include <iomanip>
37
38 #include "Backtrace.hh"
39 #include "Logging.hh"
40 #include "Svar_DB.hh"
41 #include "Svar_signals.hh"
42 #include "UserPreferences.hh"
43
44 extern ostream & get_CERR();
45
46 uint16_t Svar_DB::APserver_port = APSERVER_PORT;
47
48 TCP_socket Svar_DB::DB_tcp = NO_TCP_SOCKET;
49
50 Svar_record Svar_record_P::cache;
51
52 //-----------------------------------------------------------------------------
53 bool
start_APserver(const char * server_sockname,const char * bin_dir,bool logit)54 Svar_DB::start_APserver(const char * server_sockname,
55 const char * bin_dir, bool logit)
56 {
57 // bin_dir is the directory where the apl interpreter binary lives.
58 // The APserver then lives in:
59 //
60 // bin_dir/ if apl was built and installed, or
61 // bin_dir/APs/ if apl was started from the src directory in the source tree
62 //
63 // set APserver_path to the case that applies.
64 //
65 char APserver_path[APL_PATH_MAX + 1];
66 const int slen = snprintf(APserver_path, APL_PATH_MAX, "%s/APserver", bin_dir);
67 if (slen >= APL_PATH_MAX) APserver_path[APL_PATH_MAX] = 0;
68
69 APserver_path[APL_PATH_MAX] = 0;
70 if (access(APserver_path, X_OK) != 0) // no APserver
71 {
72 logit && get_CERR() << " Executable " << APserver_path
73 << " not found (this is OK when apl was started\n"
74 " from the src directory): " << strerror(errno) << endl;
75
76 const int slen = snprintf(APserver_path, APL_PATH_MAX,
77 "%s/APs/APserver", bin_dir);
78 if (slen >= APL_PATH_MAX) APserver_path[APL_PATH_MAX] = 0;
79 if (access(APserver_path, X_OK) != 0) // no APs/APserver either
80 {
81 get_CERR() << "Executable " << APserver_path << " not found.\n"
82 "This could means that 'apl' was not installed ('make install') or that it\n"
83 "was started in a non-standard way. The expected location of APserver is \n"
84 "either the same directory as the binary 'apl' or the subdirectory 'APs' of\n"
85 "that directory (the directory should also be in $PATH)." << endl;
86
87 return true; // error
88 }
89 }
90
91 logit && get_CERR() << "Found " << APserver_path << endl;
92
93 char popen_args[APL_PATH_MAX + 1];
94 {
95 int slen;
96 if (server_sockname)
97 slen = snprintf(popen_args, APL_PATH_MAX,
98 "%s --path %s --auto", APserver_path, server_sockname);
99 else
100 slen = snprintf(popen_args, APL_PATH_MAX,
101 "%s --port %u --auto", APserver_path, APserver_port);
102 if (slen >= APL_PATH_MAX) popen_args[APL_PATH_MAX] = 0;
103 }
104
105 logit && get_CERR() << "Starting " << popen_args << "..." << endl;
106
107 FILE * fp = popen(popen_args, "r");
108 if (fp == 0)
109 {
110 get_CERR() << "popen(" << popen_args << " failed: " << strerror(errno)
111 << endl;
112 return true; // error
113 }
114
115 for (int cc; (cc = getc(fp)) != EOF;)
116 {
117 logit && get_CERR() << char(cc);
118 }
119
120 logit && get_CERR() << endl;
121
122 const int APserver_result = pclose(fp);
123 if (APserver_result)
124 {
125 get_CERR() << "pclose(APserver) returned error:"
126 << strerror(errno) << endl;
127 }
128
129 return false; // success
130 }
131 //-----------------------------------------------------------------------------
132 TCP_socket
connect_to_APserver(const char * bin_dir,const char * prog,int retry_max,bool logit)133 Svar_DB::connect_to_APserver(const char * bin_dir, const char * prog,
134 int retry_max, bool logit)
135 {
136 int sock = NO_TCP_SOCKET;
137 const char * server_sockname = APSERVER_PATH;
138 char peer[100];
139
140 // we use AF_UNIX sockets if the platform supports it and unix_socket_name
141 // is provided. Otherwise fall back to TCP.
142 //
143 #if HAVE_SYS_UN_H && APSERVER_TRANSPORT != 0
144 {
145 logit && get_CERR() << prog
146 << ": Using AF_UNIX socket towards APserver..."
147 << endl;
148 sock = socket(AF_UNIX, SOCK_STREAM, 0);
149 if (sock == NO_TCP_SOCKET)
150 {
151 get_CERR() << prog
152 << ": socket(AF_UNIX, SOCK_STREAM, 0) failed at "
153 << LOC << endl;
154 return NO_TCP_SOCKET;
155 }
156
157 const unsigned int slen = snprintf(peer, sizeof(peer),
158 "%s", server_sockname);
159 if (slen >= sizeof(peer)) peer[sizeof(peer) - 1] = 0;
160 }
161 #else // use TCP
162 {
163 server_sockname = 0;
164 logit && get_CERR() << "Using TCP socket towards APserver..."
165 << endl;
166 sock = TCP_socket(socket(AF_INET, SOCK_STREAM, 0));
167 if (sock == NO_TCP_SOCKET)
168 {
169 get_CERR() << "*** socket(AF_INET, SOCK_STREAM, 0) failed at "
170 << LOC << endl;
171 return NO_TCP_SOCKET;
172 }
173
174 // disable nagle
175 {
176 const int ndelay = 1;
177 setsockopt(sock, 6, TCP_NODELAY, &ndelay, sizeof(int));
178 }
179
180 // bind local port to 127.0.0.1
181 //
182 SockAddr local;
183 memset(&local, 0, sizeof(SockAddr));
184 local.inet.sin_family = AF_INET;
185 local.inet.sin_addr.s_addr = htonl(0x7F000001);
186
187 if (::bind(sock, &local.addr, sizeof(sockaddr_in)))
188 {
189 get_CERR() << "bind(127.0.0.1) failed:" << strerror(errno) << endl;
190 ::close(sock);
191 return NO_TCP_SOCKET;
192 }
193
194 const unsigned int slen = snprintf(peer, sizeof(peer),
195 "127.0.0.1 TCP port %d", APserver_port);
196 if (slen >= sizeof(peer)) peer[sizeof(peer) - 1] = 0;
197 }
198 #endif
199
200 // We try to connect to the APserver. If that fails then no
201 // APserver is running; we fork one and try again.
202 //
203 // If emacs mode is enabled then we try a little longer since emacs starts
204 // up at the same time (and we know that emacs is slow).
205 //
206 loop(retry, retry_max)
207 {
208 #if HAVE_SYS_UN_H
209 if (server_sockname)
210 {
211 SockAddr remote;
212 memset(&remote, 0, sizeof(SockAddr));
213 remote.uNix.sun_family = AF_UNIX;
214 strcpy(remote.uNix.sun_path + ABSTRACT_OFFSET, server_sockname);
215
216 if (::connect(sock, &remote.addr, sizeof(sockaddr_un)) == 0)
217 break; // success
218 }
219 else // TCP
220 #endif
221 {
222 SockAddr remote;
223 memset(&remote, 0, sizeof(sockaddr_in));
224 remote.inet.sin_family = AF_INET;
225 remote.inet.sin_port = htons(APserver_port);
226 remote.inet.sin_addr.s_addr = htonl(0x7F000001);
227
228 if (::connect(sock, &remote.addr, sizeof(sockaddr_in)) == 0)
229 break; // success
230 }
231
232 // ::connect() to APserver failed. If
233 // then most likely no APserver was started and we do it.
234 //
235 if (logit) get_CERR() << "connecting to " << peer
236 << " failed." << endl;
237
238 if (retry == 0) // first attempt
239 {
240 // this was the first ::connect() that has failed.
241 // most likely no APserver was started yet and we do it now
242 //
243 if (logit) get_CERR() <<
244 " (the first ::connect() to APserver is expected to fail, unless\n" <<
245 " APserver was started manually)\n" <<
246 "Starting a new APserver listening on " << peer << endl;
247
248 if (start_APserver(server_sockname, bin_dir, logit))
249 {
250 // starting of APserver failed: no point to try longer
251 //
252 ::close(sock);
253 return NO_TCP_SOCKET;
254 }
255
256 usleep(300000); // give APserver some time to start up
257 continue;
258 }
259
260 if ((retry == (retry_max - 1)) && bin_dir) // last attempt: give up
261 {
262 get_CERR() <<
263 "::connect() to supposedly existing APserver failed: "
264 << strerror(errno) << endl;
265
266 ::close(sock);
267 return NO_TCP_SOCKET;
268 }
269
270 if (logit) get_CERR() <<
271 " (::connect() should succeed eventually. This was attempt "
272 << retry << " of " << retry_max << ")" << endl;
273
274 usleep(200000); // more time for APserver to start up
275 }
276
277 // at this point sock is != NO_TCP_SOCKET and connected.
278 //
279 usleep(50000);
280 logit && get_CERR() << "connected to APserver, socket is " << sock << endl;
281
282 return TCP_socket(sock);
283 }
284 //-----------------------------------------------------------------------------
285 void
DB_tcp_error(const char * op,int got,int expected)286 Svar_DB::DB_tcp_error(const char * op, int got, int expected)
287 {
288 // op == 0 indicates close
289 //
290 if (op)
291 {
292 get_CERR() << "⋆⋆⋆ " << op << " failed: got " << got << " when expecting "
293 << expected << " (" << strerror(errno) << ")" << endl;
294 }
295
296 shutdown(DB_tcp, SHUT_RDWR);
297 }
298 //=============================================================================
299
Svar_record_P(SV_key key)300 Svar_record_P::Svar_record_P(SV_key key)
301 {
302 cache.clear();
303
304 if (!Svar_DB::APserver_available()) return;
305
306 const int sock = Svar_DB::get_DB_tcp();
307
308 { READ_SVAR_RECORD_c request(sock, key); }
309
310 char * del = 0;
311 char buffer[2*MAX_SIGNAL_CLASS_SIZE + sizeof(Svar_record)];
312 ostream * log = (LOG_startup != 0 || LOG_Svar_DB_signals != 0) ? & cerr : 0;
313 Signal_base * response = Signal_base::recv_TCP(sock, buffer, sizeof(buffer),
314 del, log);
315 if (response)
316 {
317 memcpy(static_cast<void *>(&cache),
318 response->get__SVAR_RECORD_IS__record().data(),
319 sizeof(Svar_record));
320
321 delete response;
322 }
323 else get_CERR() << "Svar_record_P() failed at " << LOC << endl;
324 if (del) delete del;
325 }
326 //=============================================================================
327 void
init(const char * bin_dir,const char * prog,int retry_max,bool logit,bool do_svars)328 Svar_DB::init(const char * bin_dir, const char * prog, int retry_max,
329 bool logit, bool do_svars)
330 {
331 if (!do_svars) // shared variables disabled
332 {
333 if (logit)
334 get_CERR() << "Not opening shared memory because command "
335 "line option --noSV (or similar) was given." << endl;
336 return;
337 }
338
339 DB_tcp = connect_to_APserver(bin_dir, prog, retry_max, logit);
340 if (APserver_available())
341 {
342 if (logit) get_CERR() << "using Svar_DB on APserver!" << endl;
343 }
344 }
345 //-----------------------------------------------------------------------------
346 SV_key
match_or_make(const uint32_t * UCS_varname,const AP_num3 & to,const Svar_partner & from)347 Svar_DB::match_or_make(const uint32_t * UCS_varname, const AP_num3 & to,
348 const Svar_partner & from)
349 {
350 const TCP_socket tcp = get_Svar_DB_tcp(__FUNCTION__);
351 if (tcp == NO_TCP_SOCKET) return 0;
352
353
354 uint32_t vname1[MAX_SVAR_NAMELEN];
355 memset(vname1, 0, sizeof(vname1));
356 loop(v, MAX_SVAR_NAMELEN)
357 {
358 if (*UCS_varname) vname1[v] = *UCS_varname++;
359 else break;
360 }
361
362 string vname(charP(vname1), MAX_SVAR_NAMELEN*sizeof(uint32_t));
363
364 MATCH_OR_MAKE_c request(tcp, vname,
365 to.proc, to.parent, to.grand,
366 from.id.proc, from.id.parent, from.id.grand);
367
368 char * del = 0;
369 char buffer[2*MAX_SIGNAL_CLASS_SIZE + 16];
370 Signal_base * response = Signal_base::recv_TCP(tcp, buffer, sizeof(buffer),
371 del, 0);
372
373 if (response)
374 {
375 const SV_key ret = response->get__MATCH_OR_MAKE_RESULT__key();
376 delete response;
377 return ret;
378 }
379
380 else return 0;
381 }
382 //-----------------------------------------------------------------------------
383 SV_key
get_events(Svar_event & events,AP_num3 id)384 Svar_DB::get_events(Svar_event & events, AP_num3 id)
385 {
386 const TCP_socket tcp = get_Svar_DB_tcp(__FUNCTION__);
387 if (tcp == NO_TCP_SOCKET)
388 {
389 events = SVE_NO_EVENTS;
390 return 0;
391 }
392
393 GET_EVENTS_c request(tcp, id.proc, id.parent, id.grand);
394
395 char * del = 0;
396 char buffer[2*MAX_SIGNAL_CLASS_SIZE + 16];
397 Signal_base * response =
398 Signal_base::recv_TCP(tcp, buffer, sizeof(buffer), del, 0);
399
400 if (response)
401 {
402 events = Svar_event(response->get__EVENTS_ARE__events());
403 const SV_key ret = response->get__EVENTS_ARE__key();
404 delete response;
405 return ret;
406 }
407 else
408 {
409 events = SVE_NO_EVENTS;
410 return 0;
411 }
412 }
413 //-----------------------------------------------------------------------------
414 Svar_event
clear_all_events(AP_num3 id)415 Svar_DB::clear_all_events(AP_num3 id)
416 {
417 const TCP_socket tcp = get_Svar_DB_tcp(__FUNCTION__);
418 if (tcp == NO_TCP_SOCKET)
419 {
420 return SVE_NO_EVENTS;
421 }
422
423 CLEAR_ALL_EVENTS_c request(tcp, id.proc, id.parent, id.grand);
424
425 char * del = 0;
426 char buffer[2*MAX_SIGNAL_CLASS_SIZE + 16];
427 Signal_base * response = Signal_base::recv_TCP(tcp, buffer, sizeof(buffer),
428 del, 0);
429
430 if (response)
431 {
432 const Svar_event ret = Svar_event(response->get__EVENTS_ARE__events());
433 delete response;
434 return ret;
435 }
436 else
437 {
438 return SVE_NO_EVENTS;
439 }
440 }
441 //-----------------------------------------------------------------------------
442 void
set_control(SV_key key,Svar_Control ctl)443 Svar_DB::set_control(SV_key key, Svar_Control ctl)
444 {
445 const TCP_socket tcp = get_Svar_DB_tcp(__FUNCTION__);
446 if (tcp == NO_TCP_SOCKET) return;
447
448 SET_CONTROL_c request(tcp, key, ctl);
449 }
450 //-----------------------------------------------------------------------------
451 void
set_state(SV_key key,bool used,const char * loc)452 Svar_DB::set_state(SV_key key, bool used, const char * loc)
453 {
454 const TCP_socket tcp = get_Svar_DB_tcp(__FUNCTION__);
455 if (tcp == NO_TCP_SOCKET) return;
456
457 string sloc(loc);
458 SET_STATE_c request(tcp, key, used, sloc);
459 }
460 //-----------------------------------------------------------------------------
461 bool
may_set(SV_key key,int attempt)462 Svar_DB::may_set(SV_key key, int attempt)
463 {
464 const TCP_socket tcp = get_Svar_DB_tcp(__FUNCTION__);
465 if (tcp == NO_TCP_SOCKET) return 0;
466
467 MAY_SET_c request(tcp, key, attempt);
468
469 char * del = 0;
470 char buffer[2*MAX_SIGNAL_CLASS_SIZE + 16];
471 Signal_base * response = Signal_base::recv_TCP(tcp, buffer, sizeof(buffer),
472 del, 0);
473
474 if (response)
475 {
476 const bool ret = response->get__YES_NO__yes();
477 delete response;
478 return ret;
479 }
480
481 return true;
482 }
483 //-----------------------------------------------------------------------------
484 bool
may_use(SV_key key,int attempt)485 Svar_DB::may_use(SV_key key, int attempt)
486 {
487 const TCP_socket tcp = get_Svar_DB_tcp(__FUNCTION__);
488 if (tcp == NO_TCP_SOCKET) return 0;
489
490 MAY_USE_c request(tcp, key, attempt);
491
492 char * del = 0;
493 char buffer[2*MAX_SIGNAL_CLASS_SIZE];
494 Signal_base * response = Signal_base::recv_TCP(tcp, buffer, sizeof(buffer),
495 del, 0);
496
497 if (response)
498 {
499 const bool ret = response->get__YES_NO__yes();
500 delete response;
501 return ret;
502 }
503 return true;
504 }
505 //-----------------------------------------------------------------------------
506 void
add_event(SV_key key,AP_num3 id,Svar_event event)507 Svar_DB::add_event(SV_key key, AP_num3 id, Svar_event event)
508 {
509 const TCP_socket tcp = get_Svar_DB_tcp(__FUNCTION__);
510 if (tcp == NO_TCP_SOCKET) return;
511
512 ADD_EVENT_c request(tcp, key, id.proc, id.parent, id.grand, event);
513 }
514 //-----------------------------------------------------------------------------
515 void
retract_var(SV_key key)516 Svar_DB::retract_var(SV_key key)
517 {
518 const TCP_socket tcp = get_Svar_DB_tcp(__FUNCTION__);
519 if (tcp == NO_TCP_SOCKET) return;
520
521 RETRACT_VAR_c request(tcp, key);
522 }
523 //-----------------------------------------------------------------------------
524 AP_num3
find_offering_id(SV_key key)525 Svar_DB::find_offering_id(SV_key key)
526 {
527 AP_num3 offering_id;
528 const TCP_socket tcp = get_Svar_DB_tcp(__FUNCTION__);
529 if (tcp == NO_TCP_SOCKET) return offering_id;
530
531 FIND_OFFERING_ID_c request(tcp, key);
532
533 char * del = 0;
534 char buffer[2*MAX_SIGNAL_CLASS_SIZE + 16];
535 Signal_base * response = Signal_base::recv_TCP(tcp, buffer, sizeof(buffer),
536 del, 0);
537
538 if (response)
539 {
540 offering_id.proc = AP_num(response->get__OFFERING_ID_IS__proc());
541 offering_id.parent = AP_num(response->get__OFFERING_ID_IS__parent());
542 offering_id.grand = AP_num(response->get__OFFERING_ID_IS__grand());
543 delete response;
544 }
545
546 return offering_id;
547 }
548 //-----------------------------------------------------------------------------
549 void
get_offering_processors(AP_num to_proc,std::vector<AP_num> & processors)550 Svar_DB::get_offering_processors(AP_num to_proc,
551 std::vector<AP_num> & processors)
552 {
553 const TCP_socket tcp = get_Svar_DB_tcp(__FUNCTION__);
554 if (tcp == NO_TCP_SOCKET) return;
555
556 GET_OFFERING_PROCS_c request(tcp, to_proc);
557
558 char * del = 0;
559 char buffer[2*MAX_SIGNAL_CLASS_SIZE + 16];
560 Signal_base * response = Signal_base::recv_TCP(tcp, buffer, sizeof(buffer),
561 del, 0);
562
563 if (response)
564 {
565 const string & op = response->get__OFFERING_PROCS_ARE__offering_procs();
566 const AP_num * procs = reinterpret_cast<const AP_num *>(op.data());
567 const size_t count = op.size() / sizeof(AP_num);
568
569 loop(c, count) processors.push_back(*procs++);
570 delete response;
571 }
572 }
573 //-----------------------------------------------------------------------------
574 void
get_offered_variables(AP_num to_proc,AP_num from_proc,std::vector<uint32_t> & varnames)575 Svar_DB::get_offered_variables(AP_num to_proc, AP_num from_proc,
576 std::vector<uint32_t> & varnames)
577 {
578 const TCP_socket tcp = get_Svar_DB_tcp(__FUNCTION__);
579 if (tcp == NO_TCP_SOCKET) return;
580
581 GET_OFFERED_VARS_c request(tcp, to_proc, from_proc);
582
583 char * del = 0;
584 char buffer[2*MAX_SIGNAL_CLASS_SIZE + 16];
585 Signal_base * response = Signal_base::recv_TCP(tcp, buffer, sizeof(buffer),
586 del, 0);
587
588 if (response)
589 {
590 const string & ov = response->get__OFFERED_VARS_ARE__offered_vars();
591 const uint32_t * names = reinterpret_cast<const uint32_t *>(ov.data());
592 const size_t count = ov.size() / sizeof(uint32_t);
593
594 loop(c, count) varnames.push_back(*names++);
595 delete response;
596 }
597 }
598 //-----------------------------------------------------------------------------
599 bool
is_registered_id(const AP_num3 & id)600 Svar_DB::is_registered_id(const AP_num3 & id)
601 {
602 const TCP_socket tcp = get_Svar_DB_tcp(__FUNCTION__);
603 if (tcp == NO_TCP_SOCKET) return 0;
604
605 IS_REGISTERED_ID_c request(tcp, id.proc, id.parent, id.grand);
606
607 char * del = 0;
608 char buffer[2*MAX_SIGNAL_CLASS_SIZE + 16];
609 Signal_base * response = Signal_base::recv_TCP(tcp, buffer, sizeof(buffer),
610 del, 0);
611
612 if (response)
613 {
614 const bool ret = response->get__YES_NO__yes();
615 delete response;
616 return ret;
617 }
618
619 return false;
620 }
621 //-----------------------------------------------------------------------------
622 TCP_socket
get_Svar_DB_tcp(const char * calling_function)623 Svar_DB::get_Svar_DB_tcp(const char * calling_function)
624 {
625 const TCP_socket tcp = Svar_DB::get_DB_tcp();
626 if (tcp == NO_TCP_SOCKET)
627 {
628 get_CERR() << "Svar_DB not connected in Svar_DB::"
629 << calling_function << "()" << endl;
630 }
631
632 return tcp;
633 }
634 //-----------------------------------------------------------------------------
635 SV_key
find_pairing_key(SV_key key)636 Svar_DB::find_pairing_key(SV_key key)
637 {
638 const TCP_socket tcp = Svar_DB::get_DB_tcp();
639 if (tcp == NO_TCP_SOCKET) return 0;
640
641 FIND_PAIRING_KEY_c request(tcp, key);
642
643 char * del = 0;
644 char buffer[2*MAX_SIGNAL_CLASS_SIZE + 16];
645 Signal_base * response = Signal_base::recv_TCP(tcp, buffer, sizeof(buffer),
646 del, 0);
647
648 if (response)
649 {
650 const SV_key ret = response->get__PAIRING_KEY_IS__pairing_key();
651 delete response;
652 return ret;
653 }
654
655 return 0;
656 }
657 //-----------------------------------------------------------------------------
658 void
print(ostream & out)659 Svar_DB::print(ostream & out)
660 {
661 const TCP_socket tcp = Svar_DB::get_DB_tcp();
662 if (tcp == NO_TCP_SOCKET) return;
663
664 PRINT_SVAR_DB_c request(tcp);
665
666 char * del = 0;
667 char buffer[2*MAX_SIGNAL_CLASS_SIZE + 4000];
668 Signal_base * response = Signal_base::recv_TCP(tcp, buffer, sizeof(buffer),
669 del, 0);
670
671 if (response)
672 {
673 out << response->get__SVAR_DB_PRINTED__printout();
674 delete response;
675 }
676 }
677 //-----------------------------------------------------------------------------
678
679