Change logging to use estream. The makes logging to a socket also

work on Solaris etc.  Further changes required..  This is just a first
step.
This commit is contained in:
Werner Koch 2010-03-10 17:22:23 +00:00
parent d8b1099d01
commit 02566c5856
5 changed files with 175 additions and 122 deletions

View File

@ -1,3 +1,25 @@
2010-03-10 Werner Koch <wk@g10code.com>
* estream.c (es_func_fp_read, es_func_fp_write, es_func_fp_seek)
(es_func_fp_destroy): Allow a NULL FP to implement a dummy stream.
(do_fpopen): Ditto.
(es_vfprintf_unlocked): New.
(es_fprintf_unlocked): Make public.
(es_fputs_unlocked): New.
* logging.h: Replace FILE* by estream_t.
* logging.c: Remove USE_FUNWRITER cpp conditional because we now
use estream.
(my_funopen_hook_ret_t, my_funopen_hook_size_t): Replace by
ssize_t.
(log_get_stream): Change to return an estream_t.
(set_file_fd): Always close the log stream because it can't be
assigned to stderr or stdout directly. Use a dummy estream as
last resort log stream.
(log_test_fd, log_get_fd): Use es_fileno.
(log_get_stream): Assert that we have a log stream.
(do_logv): Use estream functions and lock the output.
2010-03-10 Werner Koch <wk@g10code.com> 2010-03-10 Werner Koch <wk@g10code.com>
* util.h: Replace jnlib path part by common. * util.h: Replace jnlib path part by common.

View File

@ -869,7 +869,10 @@ es_func_fp_read (void *cookie, void *buffer, size_t size)
estream_cookie_fp_t file_cookie = cookie; estream_cookie_fp_t file_cookie = cookie;
ssize_t bytes_read; ssize_t bytes_read;
bytes_read = fread (buffer, 1, size, file_cookie->fp); if (file_cookie->fp)
bytes_read = fread (buffer, 1, size, file_cookie->fp);
else
bytes_read = 0;
if (!bytes_read && ferror (file_cookie->fp)) if (!bytes_read && ferror (file_cookie->fp))
return -1; return -1;
return bytes_read; return bytes_read;
@ -883,7 +886,11 @@ es_func_fp_write (void *cookie, const void *buffer, size_t size)
estream_cookie_fp_t file_cookie = cookie; estream_cookie_fp_t file_cookie = cookie;
size_t bytes_written; size_t bytes_written;
bytes_written = fwrite (buffer, 1, size, file_cookie->fp);
if (file_cookie->fp)
bytes_written = fwrite (buffer, 1, size, file_cookie->fp);
else
bytes_written = size; /* Successfully written to the bit bucket. */
if (bytes_written != size) if (bytes_written != size)
return -1; return -1;
return bytes_written; return bytes_written;
@ -896,17 +903,25 @@ es_func_fp_seek (void *cookie, off_t *offset, int whence)
estream_cookie_fp_t file_cookie = cookie; estream_cookie_fp_t file_cookie = cookie;
long int offset_new; long int offset_new;
if (!file_cookie->fp)
{
_set_errno (ESPIPE);
return -1;
}
if ( fseek (file_cookie->fp, (long int)*offset, whence) ) if ( fseek (file_cookie->fp, (long int)*offset, whence) )
{ {
fprintf (stderr, "\nfseek failed: errno=%d (%s)\n", errno,strerror (errno)); /* fprintf (stderr, "\nfseek failed: errno=%d (%s)\n", */
return -1; /* errno,strerror (errno)); */
return -1;
} }
offset_new = ftell (file_cookie->fp); offset_new = ftell (file_cookie->fp);
if (offset_new == -1) if (offset_new == -1)
{ {
fprintf (stderr, "\nftell failed: errno=%d (%s)\n", errno,strerror (errno)); /* fprintf (stderr, "\nftell failed: errno=%d (%s)\n", */
return -1; /* errno,strerror (errno)); */
return -1;
} }
*offset = offset_new; *offset = offset_new;
return 0; return 0;
@ -921,8 +936,13 @@ es_func_fp_destroy (void *cookie)
if (fp_cookie) if (fp_cookie)
{ {
fflush (fp_cookie->fp); if (fp_cookie->fp)
err = fp_cookie->no_close? 0 : fclose (fp_cookie->fp); {
fflush (fp_cookie->fp);
err = fp_cookie->no_close? 0 : fclose (fp_cookie->fp);
}
else
err = 0;
mem_free (fp_cookie); mem_free (fp_cookie);
} }
else else
@ -2268,13 +2288,14 @@ do_fpopen (FILE *fp, const char *mode, int no_close)
if (err) if (err)
goto out; goto out;
fflush (fp); if (fp)
fflush (fp);
err = es_func_fp_create (&cookie, fp, modeflags, no_close); err = es_func_fp_create (&cookie, fp, modeflags, no_close);
if (err) if (err)
goto out; goto out;
create_called = 1; create_called = 1;
err = es_create (&stream, cookie, fileno (fp), estream_functions_fp, err = es_create (&stream, cookie, fp? fileno (fp):-1, estream_functions_fp,
modeflags); modeflags);
out: out:
@ -2737,6 +2758,17 @@ es_fgets (char *ES__RESTRICT buffer, int length, estream_t ES__RESTRICT stream)
} }
int
es_fputs_unlocked (const char *ES__RESTRICT s, estream_t ES__RESTRICT stream)
{
size_t length;
int err;
length = strlen (s);
err = es_writen (stream, s, length, NULL);
return err ? EOF : 0;
}
int int
es_fputs (const char *ES__RESTRICT s, estream_t ES__RESTRICT stream) es_fputs (const char *ES__RESTRICT s, estream_t ES__RESTRICT stream)
{ {
@ -2931,6 +2963,15 @@ es_free (void *a)
} }
int
es_vfprintf_unlocked (estream_t ES__RESTRICT stream,
const char *ES__RESTRICT format,
va_list ap)
{
return es_print (stream, format, ap);
}
int int
es_vfprintf (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format, es_vfprintf (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format,
va_list ap) va_list ap)
@ -2945,9 +2986,9 @@ es_vfprintf (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format,
} }
static int int
es_fprintf_unlocked (estream_t ES__RESTRICT stream, es_fprintf_unlocked (estream_t ES__RESTRICT stream,
const char *ES__RESTRICT format, ...) const char *ES__RESTRICT format, ...)
{ {
int ret; int ret;

View File

@ -113,11 +113,14 @@
#define es_fwrite _ESTREAM_PREFIX(es_fwrite) #define es_fwrite _ESTREAM_PREFIX(es_fwrite)
#define es_fgets _ESTREAM_PREFIX(es_fgets) #define es_fgets _ESTREAM_PREFIX(es_fgets)
#define es_fputs _ESTREAM_PREFIX(es_fputs) #define es_fputs _ESTREAM_PREFIX(es_fputs)
#define es_fputs_unlocked _ESTREAM_PREFIX(es_fputs_unlocked)
#define es_getline _ESTREAM_PREFIX(es_getline) #define es_getline _ESTREAM_PREFIX(es_getline)
#define es_read_line _ESTREAM_PREFIX(es_read_line) #define es_read_line _ESTREAM_PREFIX(es_read_line)
#define es_free _ESTREAM_PREFIX(es_free) #define es_free _ESTREAM_PREFIX(es_free)
#define es_fprf _ESTREAM_PREFIX(es_fprf) #define es_fprintf _ESTREAM_PREFIX(es_fprintf)
#define es_vfprf _ESTREAM_PREFIX(es_vfprf) #define es_fprintf_unlocked _ESTREAM_PREFIX(es_fprintf_unlocked)
#define es_vfprintf _ESTREAM_PREFIX(es_vfprint)
#define es_vfprintf_unlocked _ESTREAM_PREFIX(es_vfprint_unlocked)
#define es_setvbuf _ESTREAM_PREFIX(es_setvbuf) #define es_setvbuf _ESTREAM_PREFIX(es_setvbuf)
#define es_setbuf _ESTREAM_PREFIX(es_setbuf) #define es_setbuf _ESTREAM_PREFIX(es_setbuf)
#define es_tmpfile _ESTREAM_PREFIX(es_tmpfile) #define es_tmpfile _ESTREAM_PREFIX(es_tmpfile)
@ -311,6 +314,8 @@ size_t es_fwrite (const void *ES__RESTRICT ptr, size_t size, size_t memb,
char *es_fgets (char *ES__RESTRICT s, int n, estream_t ES__RESTRICT stream); char *es_fgets (char *ES__RESTRICT s, int n, estream_t ES__RESTRICT stream);
int es_fputs (const char *ES__RESTRICT s, estream_t ES__RESTRICT stream); int es_fputs (const char *ES__RESTRICT s, estream_t ES__RESTRICT stream);
int es_fputs_unlocked (const char *ES__RESTRICT s,
estream_t ES__RESTRICT stream);
ssize_t es_getline (char *ES__RESTRICT *ES__RESTRICT lineptr, ssize_t es_getline (char *ES__RESTRICT *ES__RESTRICT lineptr,
size_t *ES__RESTRICT n, size_t *ES__RESTRICT n,
@ -323,9 +328,17 @@ void es_free (void *a);
int es_fprintf (estream_t ES__RESTRICT stream, int es_fprintf (estream_t ES__RESTRICT stream,
const char *ES__RESTRICT format, ...) const char *ES__RESTRICT format, ...)
_ESTREAM_GCC_A_PRINTF(2,3); _ESTREAM_GCC_A_PRINTF(2,3);
int es_fprintf_unlocked (estream_t ES__RESTRICT stream,
const char *ES__RESTRICT format, ...)
_ESTREAM_GCC_A_PRINTF(2,3);
int es_vfprintf (estream_t ES__RESTRICT stream, int es_vfprintf (estream_t ES__RESTRICT stream,
const char *ES__RESTRICT format, va_list ap) const char *ES__RESTRICT format, va_list ap)
_ESTREAM_GCC_A_PRINTF(2,0); _ESTREAM_GCC_A_PRINTF(2,0);
int es_vfprintf_unlocked (estream_t ES__RESTRICT stream,
const char *ES__RESTRICT format, va_list ap)
_ESTREAM_GCC_A_PRINTF(2,0);
int es_setvbuf (estream_t ES__RESTRICT stream, int es_setvbuf (estream_t ES__RESTRICT stream,
char *ES__RESTRICT buf, int mode, size_t size); char *ES__RESTRICT buf, int mode, size_t size);
void es_setbuf (estream_t ES__RESTRICT stream, char *ES__RESTRICT buf); void es_setbuf (estream_t ES__RESTRICT stream, char *ES__RESTRICT buf);

View File

@ -1,6 +1,6 @@
/* logging.c - Useful logging functions /* logging.c - Useful logging functions
* Copyright (C) 1998, 1999, 2000, 2001, 2003, * Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006,
* 2004, 2005, 2006, 2009 Free Software Foundation, Inc. * 2009, 2010 Free Software Foundation, Inc.
* *
* This file is part of JNLIB. * This file is part of JNLIB.
* *
@ -43,20 +43,8 @@
#include "libjnlib-config.h" #include "libjnlib-config.h"
#include "logging.h" #include "logging.h"
#if defined (HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN)
#define USE_FUNWRITER 1
#endif
#ifdef HAVE_FOPENCOOKIE static estream_t logstream;
typedef ssize_t my_funopen_hook_ret_t;
typedef size_t my_funopen_hook_size_t;
#else
typedef int my_funopen_hook_ret_t;
typedef int my_funopen_hook_size_t;
#endif
static FILE *logstream;
static int log_socket = -1; static int log_socket = -1;
static char prefix_buffer[80]; static char prefix_buffer[80];
static int with_time; static int with_time;
@ -86,10 +74,10 @@ log_inc_errorcount (void)
} }
/* The follwing 3 functions are used by funopen to write logs to a /* The following 3 functions are used by es_fopencookie to write logs
socket. */ to a socket. */
#ifdef USE_FUNWRITER struct fun_cookie_s
struct fun_cookie_s { {
int fd; int fd;
int quiet; int quiet;
int want_socket; int want_socket;
@ -97,6 +85,7 @@ struct fun_cookie_s {
char name[1]; char name[1];
}; };
/* Write NBYTES of BUFFER to file descriptor FD. */ /* Write NBYTES of BUFFER to file descriptor FD. */
static int static int
writen (int fd, const void *buffer, size_t nbytes) writen (int fd, const void *buffer, size_t nbytes)
@ -120,8 +109,8 @@ writen (int fd, const void *buffer, size_t nbytes)
} }
static my_funopen_hook_ret_t static ssize_t
fun_writer (void *cookie_arg, const char *buffer, my_funopen_hook_size_t size) fun_writer (void *cookie_arg, const void *buffer, size_t size)
{ {
struct fun_cookie_s *cookie = cookie_arg; struct fun_cookie_s *cookie = cookie_arg;
@ -191,7 +180,7 @@ fun_writer (void *cookie_arg, const char *buffer, my_funopen_hook_size_t size)
log_socket = cookie->fd; log_socket = cookie->fd;
if (cookie->fd != -1 && !writen (cookie->fd, buffer, size)) if (cookie->fd != -1 && !writen (cookie->fd, buffer, size))
return (my_funopen_hook_ret_t)size; /* Okay. */ return (ssize_t)size; /* Okay. */
if (!running_detached && cookie->fd != -1 if (!running_detached && cookie->fd != -1
&& isatty (fileno (stderr))) && isatty (fileno (stderr)))
@ -210,9 +199,10 @@ fun_writer (void *cookie_arg, const char *buffer, my_funopen_hook_size_t size)
log_socket = -1; log_socket = -1;
} }
return (my_funopen_hook_ret_t)size; return (ssize_t)size;
} }
static int static int
fun_closer (void *cookie_arg) fun_closer (void *cookie_arg)
{ {
@ -224,8 +214,6 @@ fun_closer (void *cookie_arg)
log_socket = -1; log_socket = -1;
return 0; return 0;
} }
#endif /*USE_FUNWRITER*/
/* Common function to either set the logging to a file or a file /* Common function to either set the logging to a file or a file
@ -233,17 +221,14 @@ fun_closer (void *cookie_arg)
static void static void
set_file_fd (const char *name, int fd) set_file_fd (const char *name, int fd)
{ {
FILE *fp; estream_t fp;
int want_socket; int want_socket;
#ifdef USE_FUNWRITER
struct fun_cookie_s *cookie; struct fun_cookie_s *cookie;
#endif
/* Close an open log stream. */ /* Close an open log stream. */
if (logstream) if (logstream)
{ {
if (logstream != stderr && logstream != stdout) es_fclose (logstream);
fclose (logstream);
logstream = NULL; logstream = NULL;
} }
@ -266,7 +251,7 @@ set_file_fd (const char *name, int fd)
} }
/* Setup a new stream. */ /* Setup a new stream. */
#ifdef USE_FUNWRITER
/* The xmalloc below is justified because we can expect that this /* The xmalloc below is justified because we can expect that this
function is called only during initialization and there is no function is called only during initialization and there is no
easy way out of this error condition. */ easy way out of this error condition. */
@ -288,55 +273,44 @@ set_file_fd (const char *name, int fd)
} }
log_socket = cookie->fd; log_socket = cookie->fd;
#ifdef HAVE_FOPENCOOKIE
{ {
cookie_io_functions_t io = { NULL }; es_cookie_io_functions_t io = { NULL };
io.write = fun_writer; io.func_write = fun_writer;
io.close = fun_closer; io.func_close = fun_closer;
fp = fopencookie (cookie, "w", io); fp = es_fopencookie (cookie, "w", io);
} }
#else /*!HAVE_FOPENCOOKIE*/
fp = funopen (cookie, NULL, fun_writer, NULL, fun_closer);
#endif /*!HAVE_FOPENCOOKIE*/
#else /*!USE_FUNWRITER*/ /* On error default to a stderr based estream. */
/* The system does not feature custom streams. Thus fallback to
plain stdio. */
if (want_socket)
{
fprintf (stderr, "system does not support logging to a socket - "
"using stderr\n");
fp = stderr;
}
else if (name)
fp = fopen (name, "a");
else if (fd == 1)
fp = stdout;
else if (fd == 2)
fp = stderr;
else
fp = fdopen (fd, "a");
log_socket = -1;
#endif /*!USE_FUNWRITER*/
/* On error default to stderr. */
if (!fp) if (!fp)
{ {
if (name) fp = es_fpopen (stderr, "a");
fprintf (stderr, "failed to open log file `%s': %s\n", if (fp)
name, strerror(errno)); {
if (name)
es_fprintf (fp, "failed to open log file `%s': %s\n",
name, strerror (errno));
else
es_fprintf (fp, "failed to fdopen file descriptor %d: %s\n",
fd, strerror (errno));
}
else else
fprintf (stderr, "failed to fdopen file descriptor %d: %s\n", {
fd, strerror(errno)); fprintf (stderr, "failed to use stderr as log stream: %s\n",
/* We need to make sure that there is a log stream. We use stderr. */ strerror (errno));
fp = stderr; /* No way to log something. Create a dummy estream so that
there is something we can use. */
fp = es_fpopen (NULL, "a");
if (!fp)
{
fprintf (stderr, "fatal: failed to open dummy stream: %s\n",
strerror (errno));
abort();
}
}
} }
else
setvbuf (fp, NULL, _IOLBF, 0); es_setvbuf (fp, NULL, _IOLBF, 0);
logstream = fp; logstream = fp;
@ -412,13 +386,13 @@ log_get_prefix (unsigned int *flags)
/* This function returns true if the file descriptor FD is in use for /* This function returns true if the file descriptor FD is in use for
logging. This is preferable over a test using log_get_fd in that logging. This is preferable over a test using log_get_fd in that
it allows the logging code to use more then one file descriptor. */ it allows the logging code to use more then one file descriptor. */
int int
log_test_fd (int fd) log_test_fd (int fd)
{ {
if (logstream) if (logstream)
{ {
int tmp = fileno (logstream); int tmp = es_fileno (logstream);
if ( tmp != -1 && tmp == fd) if ( tmp != -1 && tmp == fd)
return 1; return 1;
} }
@ -430,16 +404,14 @@ log_test_fd (int fd)
int int
log_get_fd () log_get_fd ()
{ {
return fileno(logstream?logstream:stderr); return logstream? es_fileno(logstream) : -1;
} }
FILE * estream_t
log_get_stream () log_get_stream ()
{ {
/* FIXME: We should not return stderr here but initialize the log assert (logstream);
stream properly. This might break more things than using stderr, return logstream;
though */
return logstream?logstream:stderr;
} }
static void static void
@ -451,8 +423,9 @@ do_logv (int level, const char *fmt, va_list arg_ptr)
assert (logstream); assert (logstream);
} }
es_flockfile (logstream);
if (missing_lf && level != JNLIB_LOG_CONT) if (missing_lf && level != JNLIB_LOG_CONT)
putc('\n', logstream ); es_putc_unlocked ('\n', logstream );
missing_lf = 0; missing_lf = 0;
if (level != JNLIB_LOG_CONT) if (level != JNLIB_LOG_CONT)
@ -464,28 +437,28 @@ do_logv (int level, const char *fmt, va_list arg_ptr)
time_t atime = time (NULL); time_t atime = time (NULL);
tp = localtime (&atime); tp = localtime (&atime);
fprintf (logstream, "%04d-%02d-%02d %02d:%02d:%02d ", es_fprintf_unlocked (logstream, "%04d-%02d-%02d %02d:%02d:%02d ",
1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
tp->tm_hour, tp->tm_min, tp->tm_sec ); tp->tm_hour, tp->tm_min, tp->tm_sec );
} }
if (with_prefix || force_prefixes) if (with_prefix || force_prefixes)
fputs (prefix_buffer, logstream); es_fputs_unlocked (prefix_buffer, logstream);
if (with_pid || force_prefixes) if (with_pid || force_prefixes)
{ {
if (get_tid_callback) if (get_tid_callback)
fprintf (logstream, "[%u.%lx]", es_fprintf_unlocked (logstream, "[%u.%lx]",
(unsigned int)getpid (), get_tid_callback ()); (unsigned int)getpid (), get_tid_callback ());
else else
fprintf (logstream, "[%u]", (unsigned int)getpid ()); es_fprintf_unlocked (logstream, "[%u]", (unsigned int)getpid ());
} }
if (!with_time || force_prefixes) if (!with_time || force_prefixes)
putc (':', logstream); es_putc_unlocked (':', logstream);
/* A leading backspace suppresses the extra space so that we can /* A leading backspace suppresses the extra space so that we can
correctly output, programname, filename and linenumber. */ correctly output, programname, filename and linenumber. */
if (fmt && *fmt == '\b') if (fmt && *fmt == '\b')
fmt++; fmt++;
else else
putc (' ', logstream); es_putc_unlocked (' ', logstream);
} }
switch (level) switch (level)
@ -495,38 +468,40 @@ do_logv (int level, const char *fmt, va_list arg_ptr)
case JNLIB_LOG_INFO: break; case JNLIB_LOG_INFO: break;
case JNLIB_LOG_WARN: break; case JNLIB_LOG_WARN: break;
case JNLIB_LOG_ERROR: break; case JNLIB_LOG_ERROR: break;
case JNLIB_LOG_FATAL: fputs("Fatal: ",logstream ); break; case JNLIB_LOG_FATAL: es_fputs_unlocked ("Fatal: ",logstream ); break;
case JNLIB_LOG_BUG: fputs("Ohhhh jeeee: ", logstream); break; case JNLIB_LOG_BUG: es_fputs_unlocked ("Ohhhh jeeee: ", logstream); break;
case JNLIB_LOG_DEBUG: fputs("DBG: ", logstream ); break; case JNLIB_LOG_DEBUG: es_fputs_unlocked ("DBG: ", logstream ); break;
default: fprintf(logstream,"[Unknown log level %d]: ", level ); break; default:
es_fprintf_unlocked (logstream,"[Unknown log level %d]: ", level);
break;
} }
if (fmt) if (fmt)
{ {
vfprintf(logstream,fmt,arg_ptr) ; es_vfprintf_unlocked (logstream, fmt, arg_ptr);
if (*fmt && fmt[strlen(fmt)-1] != '\n') if (*fmt && fmt[strlen(fmt)-1] != '\n')
missing_lf = 1; missing_lf = 1;
#ifdef HAVE_W32_SYSTEM
else
fflush (logstream);
#endif
} }
if (level == JNLIB_LOG_FATAL) if (level == JNLIB_LOG_FATAL)
{ {
if (missing_lf) if (missing_lf)
putc('\n', logstream ); es_putc_unlocked ('\n', logstream);
exit(2); es_funlockfile (logstream);
exit (2);
} }
if (level == JNLIB_LOG_BUG) else if (level == JNLIB_LOG_BUG)
{ {
if (missing_lf) if (missing_lf)
putc('\n', logstream ); es_putc_unlocked ('\n', logstream );
abort(); es_funlockfile (logstream);
abort ();
} }
else
es_funlockfile (logstream);
} }
static void static void
do_log( int level, const char *fmt, ... ) do_log( int level, const char *fmt, ... )
{ {

View File

@ -1,5 +1,6 @@
/* logging.h /* logging.h
* Copyright (C) 1999, 2000, 2001, 2004, 2006 Free Software Foundation, Inc. * Copyright (C) 1999, 2000, 2001, 2004, 2006,
* 2010 Free Software Foundation, Inc.
* *
* This file is part of JNLIB. * This file is part of JNLIB.
* *
@ -21,6 +22,7 @@
#define LIBJNLIB_LOGGING_H #define LIBJNLIB_LOGGING_H
#include <stdio.h> #include <stdio.h>
#include "estream.h"
#include "mischelp.h" #include "mischelp.h"
/* Flag values for log_set_prefix. */ /* Flag values for log_set_prefix. */
@ -38,7 +40,7 @@ void log_set_prefix (const char *text, unsigned int flags);
const char *log_get_prefix (unsigned int *flags); const char *log_get_prefix (unsigned int *flags);
int log_test_fd (int fd); int log_test_fd (int fd);
int log_get_fd(void); int log_get_fd(void);
FILE *log_get_stream (void); estream_t log_get_stream (void);
#ifdef JNLIB_GCC_M_FUNCTION #ifdef JNLIB_GCC_M_FUNCTION
void bug_at( const char *file, int line, const char *func ) JNLIB_GCC_A_NR; void bug_at( const char *file, int line, const char *func ) JNLIB_GCC_A_NR;