1 /* -*- mode: C; c-basic-offset: 3; -*- */
2 
3 /*--------------------------------------------------------------------*/
4 /*--- Libc printing.                                 m_libcprint.c ---*/
5 /*--------------------------------------------------------------------*/
6 
7 /*
8    This file is part of Valgrind, a dynamic binary instrumentation
9    framework.
10 
11    Copyright (C) 2000-2015 Julian Seward
12       jseward@acm.org
13 
14    This program is free software; you can redistribute it and/or
15    modify it under the terms of the GNU General Public License as
16    published by the Free Software Foundation; either version 2 of the
17    License, or (at your option) any later version.
18 
19    This program is distributed in the hope that it will be useful, but
20    WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22    General Public License for more details.
23 
24    You should have received a copy of the GNU General Public License
25    along with this program; if not, write to the Free Software
26    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
27    02111-1307, USA.
28 
29    The GNU General Public License is contained in the file COPYING.
30 */
31 
32 #include "pub_core_basics.h"
33 #include "pub_core_vki.h"
34 #include "pub_core_debuglog.h"
35 #include "pub_core_gdbserver.h"  // VG_(gdb_printf)
36 #include "pub_core_libcbase.h"
37 #include "pub_core_libcassert.h"
38 #include "pub_core_libcfile.h"   // VG_(write)(), VG_(write_socket)()
39 #include "pub_core_libcprint.h"
40 #include "pub_core_libcproc.h"   // VG_(getpid)(), VG_(read_millisecond_timer()
41 #include "pub_core_mallocfree.h" // VG_(malloc)
42 #include "pub_core_options.h"
43 #include "pub_core_clreq.h"      // For RUNNING_ON_VALGRIND
44 
45 
46 /* ---------------------------------------------------------------------
47    Writing to file or a socket
48    ------------------------------------------------------------------ */
49 
50 /* The destination sinks for normal and XML output.  These have their
51    initial values here; they are set to final values by
52    m_main.main_process_cmd_line_options().  See comment at the top of
53    that function for the associated logic.
54    After startup, the gdbserver monitor command might temporarily
55    set the fd of log_output_sink to -2 to indicate that output is
56    to be given to gdb rather than output to the startup fd */
57 OutputSink VG_(log_output_sink) = {  2, False }; /* 2 = stderr */
58 OutputSink VG_(xml_output_sink) = { -1, False }; /* disabled */
59 
60 /* Do the low-level send of a message to the logging sink. */
61 static
send_bytes_to_logging_sink(OutputSink * sink,const HChar * msg,Int nbytes)62 void send_bytes_to_logging_sink ( OutputSink* sink, const HChar* msg, Int nbytes )
63 {
64    if (sink->is_socket) {
65       Int rc = VG_(write_socket)( sink->fd, msg, nbytes );
66       if (rc == -1) {
67          // For example, the listener process died.  Switch back to stderr.
68          sink->is_socket = False;
69          sink->fd = 2;
70          VG_(write)( sink->fd, msg, nbytes );
71       }
72    } else {
73       /* sink->fd could have been set to -1 in the various
74          sys-wrappers for sys_fork, if --child-silent-after-fork=yes
75          is in effect.  That is a signal that we should not produce
76          any more output. */
77       if (sink->fd >= 0)
78          VG_(write)( sink->fd, msg, nbytes );
79       else if (sink->fd == -2 && nbytes > 0)
80          /* send to gdb the provided data, which must be
81             a null terminated string with len >= 1 */
82          VG_(gdb_printf)("%s", msg);
83    }
84 }
85 
86 
87 /* ---------------------------------------------------------------------
88    printf() and friends
89    ------------------------------------------------------------------ */
90 
91 /* --------- printf --------- */
92 
93 typedef
94    struct {
95       HChar       buf[512];
96       Int         buf_used;
97       OutputSink* sink;
98    }
99    printf_buf_t;
100 
101 // Adds a single char to the buffer.  When the buffer gets sufficiently
102 // full, we write its contents to the logging sink.
add_to__printf_buf(HChar c,void * p)103 static void add_to__printf_buf ( HChar c, void *p )
104 {
105    printf_buf_t *b = (printf_buf_t *)p;
106 
107    if (b->buf_used > sizeof(b->buf) - 2 ) {
108       send_bytes_to_logging_sink( b->sink, b->buf, b->buf_used );
109       b->buf_used = 0;
110    }
111    b->buf[b->buf_used++] = c;
112    b->buf[b->buf_used]   = 0;
113    vg_assert(b->buf_used < sizeof(b->buf));
114 }
115 
vprintf_to_buf(printf_buf_t * b,const HChar * format,va_list vargs)116 static UInt vprintf_to_buf ( printf_buf_t* b,
117                              const HChar *format, va_list vargs )
118 {
119    UInt ret = 0;
120    if (b->sink->fd >= 0 || b->sink->fd == -2) {
121       ret = VG_(debugLog_vprintf)
122                ( add_to__printf_buf, b, format, vargs );
123    }
124    return ret;
125 }
126 
vprintf_WRK(OutputSink * sink,const HChar * format,va_list vargs)127 static UInt vprintf_WRK ( OutputSink* sink,
128                           const HChar *format, va_list vargs )
129 {
130    printf_buf_t myprintf_buf
131       = { "", 0, sink };
132    UInt ret
133       = vprintf_to_buf(&myprintf_buf, format, vargs);
134    // Write out any chars left in the buffer.
135    if (myprintf_buf.buf_used > 0) {
136       send_bytes_to_logging_sink( myprintf_buf.sink,
137                                   myprintf_buf.buf,
138                                   myprintf_buf.buf_used );
139    }
140    return ret;
141 }
142 
VG_(vprintf)143 UInt VG_(vprintf) ( const HChar *format, va_list vargs )
144 {
145    return vprintf_WRK( &VG_(log_output_sink), format, vargs );
146 }
147 
VG_(printf)148 UInt VG_(printf) ( const HChar *format, ... )
149 {
150    UInt ret;
151    va_list vargs;
152    va_start(vargs, format);
153    ret = VG_(vprintf)(format, vargs);
154    va_end(vargs);
155    return ret;
156 }
157 
VG_(vprintf_xml)158 UInt VG_(vprintf_xml) ( const HChar *format, va_list vargs )
159 {
160    return vprintf_WRK( &VG_(xml_output_sink), format, vargs );
161 }
162 
VG_(printf_xml)163 UInt VG_(printf_xml) ( const HChar *format, ... )
164 {
165    UInt ret;
166    va_list vargs;
167    va_start(vargs, format);
168    ret = VG_(vprintf_xml)(format, vargs);
169    va_end(vargs);
170    return ret;
171 }
172 
emit_WRK(const HChar * format,va_list vargs)173 static UInt emit_WRK ( const HChar* format, va_list vargs )
174 {
175    if (VG_(clo_xml)) {
176       return VG_(vprintf_xml)(format, vargs);
177    } else if (VG_(log_output_sink).fd == -2) {
178       return VG_(vprintf) (format, vargs);
179    } else {
180       return VG_(vmessage)(Vg_UserMsg, format, vargs);
181    }
182 }
VG_(emit)183 UInt VG_(emit) ( const HChar* format, ... )
184 {
185    UInt ret;
186    va_list vargs;
187    va_start(vargs, format);
188    ret = emit_WRK(format, vargs);
189    va_end(vargs);
190    return ret;
191 }
192 
193 /* --------- sprintf --------- */
194 
195 /* If we had an explicit buf structure here, it would contain only one
196    field, indicating where the next char is to go.  So use p directly
197    for that, rather than having it be a pointer to a structure. */
198 
add_to__sprintf_buf(HChar c,void * p)199 static void add_to__sprintf_buf ( HChar c, void *p )
200 {
201    HChar** b = p;
202    *(*b)++ = c;
203 }
204 
VG_(vsprintf)205 UInt VG_(vsprintf) ( HChar* buf, const HChar *format, va_list vargs )
206 {
207    Int ret;
208    HChar* sprintf_ptr = buf;
209 
210    ret = VG_(debugLog_vprintf)
211             ( add_to__sprintf_buf, &sprintf_ptr, format, vargs );
212    add_to__sprintf_buf('\0', &sprintf_ptr);
213 
214    vg_assert(VG_(strlen)(buf) == ret);
215 
216    return ret;
217 }
218 
VG_(sprintf)219 UInt VG_(sprintf) ( HChar* buf, const HChar *format, ... )
220 {
221    UInt ret;
222    va_list vargs;
223    va_start(vargs,format);
224    ret = VG_(vsprintf)(buf, format, vargs);
225    va_end(vargs);
226    return ret;
227 }
228 
229 
230 /* --------- snprintf --------- */
231 
232 /* The return value of VG_(snprintf) and VG_(vsnprintf) differs from
233    what is defined in C99. Let S be the size of the buffer as given in
234    the 2nd argument.
235    Return value R:
236      R < S:  The output string was successfully written to the buffer.
237              It is null-terminated and R == strlen( output string )
238      R == S: The supplied buffer was too small to hold the output string.
239              The first S-1 characters of the output string were written
240              to the buffer followed by the terminating null character.
241 */
242 
243 typedef
244    struct {
245       HChar* buf;
246       Int    buf_size;
247       Int    buf_used;
248    }
249    snprintf_buf_t;
250 
add_to__snprintf_buf(HChar c,void * p)251 static void add_to__snprintf_buf ( HChar c, void* p )
252 {
253    snprintf_buf_t* b = p;
254    if (b->buf_size > 0 && b->buf_used < b->buf_size) {
255       b->buf[b->buf_used++] = c;
256       if (b->buf_used < b->buf_size)
257          b->buf[b->buf_used] = 0;
258       else
259          b->buf[b->buf_size-1] = 0; /* pre: b->buf_size > 0 */
260    }
261 }
262 
VG_(vsnprintf)263 UInt VG_(vsnprintf) ( HChar* buf, Int size, const HChar *format, va_list vargs )
264 {
265    snprintf_buf_t b;
266    b.buf      = buf;
267    b.buf_size = size < 0 ? 0 : size;
268    b.buf_used = 0;
269    if (b.buf_size > 0)
270       b.buf[0] = 0; // ensure to null terminate buf if empty format
271    (void) VG_(debugLog_vprintf)
272              ( add_to__snprintf_buf, &b, format, vargs );
273 
274    return b.buf_used;
275 }
276 
VG_(snprintf)277 UInt VG_(snprintf) ( HChar* buf, Int size, const HChar *format, ... )
278 {
279    UInt ret;
280    va_list vargs;
281    va_start(vargs,format);
282    ret = VG_(vsnprintf)(buf, size, format, vargs);
283    va_end(vargs);
284    return ret;
285 }
286 
287 
288 /* --------- vcbprintf --------- */
289 
VG_(vcbprintf)290 void VG_(vcbprintf)( void(*char_sink)(HChar, void* opaque),
291                      void* opaque,
292                      const HChar* format, va_list vargs )
293 {
294    (void) VG_(debugLog_vprintf)
295              ( char_sink, opaque, format, vargs );
296 }
297 
298 
299 /* --------- fprintf ---------- */
300 
301 /* This is like [v]fprintf, except it writes to a file handle using
302    VG_(write). */
303 
304 #define VGFILE_BUFSIZE  8192
305 
306 struct _VgFile {
307    HChar buf[VGFILE_BUFSIZE];
308    UInt  num_chars;   // number of characters in buf
309    Int   fd;          // file descriptor to write to
310 };
311 
312 
add_to__vgfile(HChar c,void * p)313 static void add_to__vgfile ( HChar c, void *p )
314 {
315    VgFile *fp = p;
316 
317    fp->buf[fp->num_chars++] = c;
318 
319    if (fp->num_chars == VGFILE_BUFSIZE) {
320       VG_(write)(fp->fd, fp->buf, fp->num_chars);
321       fp->num_chars = 0;
322    }
323 }
324 
VG_(fopen)325 VgFile *VG_(fopen)(const HChar *name, Int flags, Int mode)
326 {
327    SysRes res = VG_(open)(name, flags, mode);
328 
329    if (sr_isError(res))
330       return NULL;
331 
332    VgFile *fp = VG_(malloc)("fopen", sizeof(VgFile));
333 
334    fp->fd = sr_Res(res);
335    fp->num_chars = 0;
336 
337    return fp;
338 }
339 
340 
VG_(vfprintf)341 UInt VG_(vfprintf) ( VgFile *fp, const HChar *format, va_list vargs )
342 {
343    return VG_(debugLog_vprintf)(add_to__vgfile, fp, format, vargs);
344 }
345 
VG_(fprintf)346 UInt VG_(fprintf) ( VgFile *fp, const HChar *format, ... )
347 {
348    UInt ret;
349    va_list vargs;
350    va_start(vargs,format);
351    ret = VG_(vfprintf)(fp, format, vargs);
352    va_end(vargs);
353    return ret;
354 }
355 
VG_(fclose)356 void VG_(fclose)( VgFile *fp )
357 {
358    // Flush the buffer.
359    if (fp->num_chars)
360       VG_(write)(fp->fd, fp->buf, fp->num_chars);
361 
362    VG_(free)(fp);
363 }
364 
365 
366 /* ---------------------------------------------------------------------
367    elapsed_wallclock_time()
368    ------------------------------------------------------------------ */
369 
370 /* Get the elapsed wallclock time since startup into buf, which must
371    16 chars long.  This is unchecked.  It also relies on the
372    millisecond timer having been set to zero by an initial read in
373    m_main during startup. */
374 
VG_(elapsed_wallclock_time)375 void VG_(elapsed_wallclock_time) ( /*OUT*/HChar* buf, SizeT bufsize )
376 {
377    UInt t, ms, s, mins, hours, days;
378 
379    vg_assert(bufsize > 20);
380 
381    t  = VG_(read_millisecond_timer)(); /* milliseconds */
382 
383    ms = t % 1000;
384    t /= 1000; /* now in seconds */
385 
386    s = t % 60;
387    t /= 60; /* now in minutes */
388 
389    mins = t % 60;
390    t /= 60; /* now in hours */
391 
392    hours = t % 24;
393    t /= 24; /* now in days */
394 
395    days = t;
396 
397    VG_(sprintf)(buf, "%02u:%02u:%02u:%02u.%03u ", days, hours, mins, s, ms);
398 }
399 
400 
401 /* ---------------------------------------------------------------------
402    message()
403    ------------------------------------------------------------------ */
404 
405 /* A buffer for accumulating VG_(message) style output.  This is
406    pretty much the same as VG_(printf)'s scheme, with two differences:
407 
408    * The message buffer persists between calls, so that multiple
409      calls to VG_(message) can build up output.
410 
411    * Whenever the first character on a line is emitted, the
412      ==PID== style preamble is stuffed in before it.
413 */
414 typedef
415    struct {
416       HChar buf[512+128];
417       Int   buf_used;
418       Bool  atLeft; /* notionally, is the next char position at the
419                        leftmost column? */
420       /* Current message kind - changes from call to call */
421       VgMsgKind kind;
422       /* destination */
423       OutputSink* sink;
424    }
425    vmessage_buf_t;
426 
427 static vmessage_buf_t vmessage_buf
428    = { "", 0, True, Vg_UserMsg, &VG_(log_output_sink) };
429 
430 
431 // Adds a single char to the buffer.  We aim to have at least 128
432 // bytes free in the buffer, so that it's always possible to emit
433 // the preamble into the buffer if c happens to be the character
434 // following a \n.  When the buffer gets too full, we write its
435 // contents to the logging sink.
add_to__vmessage_buf(HChar c,void * p)436 static void add_to__vmessage_buf ( HChar c, void *p )
437 {
438    HChar tmp[64];
439    vmessage_buf_t* b = (vmessage_buf_t*)p;
440 
441    vg_assert(b->buf_used >= 0 && b->buf_used < sizeof(b->buf)-128);
442 
443    if (UNLIKELY(b->atLeft)) {
444       // insert preamble
445       HChar ch;
446       Int   i, depth;
447 
448       // Print one '>' in front of the messages for each level of
449       // self-hosting being performed.
450       // Do not print such '>' if sim hint "no-inner-prefix" given
451       // (useful to run regression tests in an outer/inner setup
452       // and avoid the diff failing due to these unexpected '>').
453       depth = RUNNING_ON_VALGRIND;
454       if (depth > 0
455           && !SimHintiS(SimHint_no_inner_prefix, VG_(clo_sim_hints))) {
456          if (depth > 10)
457             depth = 10; // ?!?!
458          for (i = 0; i < depth; i++) {
459             b->buf[b->buf_used++] = '>';
460          }
461       }
462 
463       if (Vg_FailMsg == b->kind) {
464          // "valgrind: " prefix.
465          b->buf[b->buf_used++] = 'v';
466          b->buf[b->buf_used++] = 'a';
467          b->buf[b->buf_used++] = 'l';
468          b->buf[b->buf_used++] = 'g';
469          b->buf[b->buf_used++] = 'r';
470          b->buf[b->buf_used++] = 'i';
471          b->buf[b->buf_used++] = 'n';
472          b->buf[b->buf_used++] = 'd';
473          b->buf[b->buf_used++] = ':';
474          b->buf[b->buf_used++] = ' ';
475       } else {
476          switch (b->kind) {
477             case Vg_UserMsg:       ch = '='; break;
478             case Vg_DebugMsg:      ch = '-'; break;
479             case Vg_ClientMsg:     ch = '*'; break;
480             default:               ch = '?'; break;
481          }
482 
483          b->buf[b->buf_used++] = ch;
484          b->buf[b->buf_used++] = ch;
485 
486          if (VG_(clo_time_stamp)) {
487             VG_(elapsed_wallclock_time)(tmp, sizeof tmp);
488             for (i = 0; tmp[i]; i++)
489                b->buf[b->buf_used++] = tmp[i];
490          }
491 
492          VG_(sprintf)(tmp, "%d", VG_(getpid)());
493          tmp[sizeof(tmp)-1] = 0;
494          for (i = 0; tmp[i]; i++)
495             b->buf[b->buf_used++] = tmp[i];
496 
497          b->buf[b->buf_used++] = ch;
498          b->buf[b->buf_used++] = ch;
499          b->buf[b->buf_used++] = ' ';
500       }
501 
502       /* We can't possibly have stuffed 96 chars in merely as a result
503          of making the preamble (can we?) */
504       vg_assert(b->buf_used < sizeof(b->buf)-32);
505    }
506 
507    b->buf[b->buf_used++] = c;
508    b->buf[b->buf_used]   = 0;
509 
510    if (b->buf_used >= sizeof(b->buf) - 128) {
511       send_bytes_to_logging_sink( b->sink, b->buf, b->buf_used );
512       b->buf_used = 0;
513    }
514 
515    b->atLeft = c == '\n';
516 }
517 
518 
VG_(vmessage)519 UInt VG_(vmessage) ( VgMsgKind kind, const HChar* format, va_list vargs )
520 {
521    UInt ret;
522 
523    /* Note (carefully) that the buf persists from call to call, unlike
524       with the other printf variants in earlier parts of this file. */
525    vmessage_buf_t* b = &vmessage_buf; /* shorthand for convenience */
526 
527    /* We have to set this each call, so that the correct flavour
528       of preamble is emitted at each \n. */
529    b->kind = kind;
530 
531    ret = VG_(debugLog_vprintf) ( add_to__vmessage_buf,
532                                  b, format, vargs );
533 
534    /* If the message finished exactly with a \n, then flush it at this
535       point.  If not, assume more bits of the same line will turn up
536       in later messages, so don't bother to flush it right now. */
537 
538    if (b->atLeft && b->buf_used > 0) {
539       send_bytes_to_logging_sink( b->sink, b->buf, b->buf_used );
540       b->buf_used = 0;
541    }
542 
543    return ret;
544 }
545 
546 /* Send a simple single-part message. */
VG_(message)547 UInt VG_(message) ( VgMsgKind kind, const HChar* format, ... )
548 {
549    UInt count;
550    va_list vargs;
551    va_start(vargs,format);
552    count = VG_(vmessage) ( kind, format, vargs );
553    va_end(vargs);
554    return count;
555 }
556 
revert_to_stderr(void)557 static void revert_to_stderr ( void )
558 {
559    VG_(log_output_sink).fd = 2; /* stderr */
560    VG_(log_output_sink).is_socket = False;
561 }
562 
563 /* VG_(message) variants with hardwired first argument. */
564 
VG_(fmsg)565 UInt VG_(fmsg) ( const HChar* format, ... )
566 {
567    UInt count;
568    va_list vargs;
569    va_start(vargs,format);
570    count = VG_(vmessage) ( Vg_FailMsg, format, vargs );
571    va_end(vargs);
572    return count;
573 }
574 
VG_(fmsg_bad_option)575 void VG_(fmsg_bad_option) ( const HChar* opt, const HChar* format, ... )
576 {
577    va_list vargs;
578    va_start(vargs,format);
579    revert_to_stderr();
580    VG_(message) (Vg_FailMsg, "Bad option: %s\n", opt);
581    VG_(vmessage)(Vg_FailMsg, format, vargs );
582    VG_(message) (Vg_FailMsg, "Use --help for more information or consult the user manual.\n");
583    va_end(vargs);
584    VG_(exit)(1);
585 }
586 
VG_(fmsg_unknown_option)587 void VG_(fmsg_unknown_option) ( const HChar* opt)
588 {
589    revert_to_stderr();
590    VG_(message) (Vg_FailMsg, "Unknown option: %s\n", opt);
591    VG_(message) (Vg_FailMsg, "Use --help for more information or consult the user manual.\n");
592    VG_(exit)(1);
593 }
594 
VG_(umsg)595 UInt VG_(umsg) ( const HChar* format, ... )
596 {
597    UInt count;
598    va_list vargs;
599    va_start(vargs,format);
600    count = VG_(vmessage) ( Vg_UserMsg, format, vargs );
601    va_end(vargs);
602    return count;
603 }
604 
VG_(dmsg)605 UInt VG_(dmsg) ( const HChar* format, ... )
606 {
607    UInt count;
608    va_list vargs;
609    va_start(vargs,format);
610    count = VG_(vmessage) ( Vg_DebugMsg, format, vargs );
611    va_end(vargs);
612    return count;
613 }
614 
615 /* Flush any output that has accumulated in vmessage_buf as a
616    result of previous calls to VG_(message) et al. */
VG_(message_flush)617 void VG_(message_flush) ( void )
618 {
619    vmessage_buf_t* b = &vmessage_buf;
620    send_bytes_to_logging_sink( b->sink, b->buf, b->buf_used );
621    b->buf_used = 0;
622 }
623 
624 __attribute__((noreturn))
VG_(err_missing_prog)625 void VG_(err_missing_prog) ( void  )
626 {
627    revert_to_stderr();
628    VG_(fmsg)("no program specified\n");
629    VG_(fmsg)("Use --help for more information.\n");
630    VG_(exit)(1);
631 }
632 
633 __attribute__((noreturn))
VG_(err_config_error)634 void VG_(err_config_error) ( const HChar* format, ... )
635 {
636    va_list vargs;
637    va_start(vargs,format);
638    revert_to_stderr();
639    VG_(message) (Vg_FailMsg, "Startup or configuration error:\n   ");
640    VG_(vmessage)(Vg_FailMsg, format, vargs );
641    VG_(message) (Vg_FailMsg, "Unable to start up properly.  Giving up.\n");
642    va_end(vargs);
643    VG_(exit)(1);
644 }
645 
646 /* ---------------------------------------------------------------------
647    VG_(sr_as_string)()
648    ------------------------------------------------------------------ */
649 
650 #if defined(VGO_linux)
651 // FIXME: Does this function need to be adjusted for MIPS's _valEx ?
VG_(sr_as_string)652 const HChar *VG_(sr_as_string) ( SysRes sr )
653 {
654    static HChar buf[7+1+2+16+1+1];   // large enough
655 
656    if (sr_isError(sr))
657       VG_(sprintf)(buf, "Failure(0x%lx)", sr_Err(sr));
658    else
659       VG_(sprintf)(buf, "Success(0x%lx)", sr_Res(sr));
660    return buf;
661 }
662 
663 #elif defined(VGO_darwin) || (VGO_solaris)
664 
VG_(sr_as_string)665 const HChar *VG_(sr_as_string) ( SysRes sr )
666 {
667    static HChar buf[7+1+2+16+1+2+16+1+1];   // large enough
668 
669    if (sr_isError(sr))
670       VG_(sprintf)(buf, "Failure(0x%lx)", sr_Err(sr));
671    else
672       VG_(sprintf)(buf, "Success(0x%lx:0x%lx)", sr_ResHI(sr), sr_Res(sr));
673    return buf;
674 }
675 
676 #else
677 
678 #error unknown OS
679 
680 #endif
681 
682 /*--------------------------------------------------------------------*/
683 /*--- end                                                          ---*/
684 /*--------------------------------------------------------------------*/
685