From: Harlan Stenn Date: Sun, 20 May 2007 20:32:43 +0000 (-0400) Subject: Initial import of libisc/log.c and friends X-Git-Tag: NTP_4_2_5P35~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bdaa99c96ae4f20940e190fa889c4339b40ca195;p=thirdparty%2Fntp.git Initial import of libisc/log.c and friends bk: 4650b06b3lcX5kQV6XaX6NHkOp9VWg --- diff --git a/ChangeLog b/ChangeLog index 62cb4e459..69a3e885a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ +* Initial import of libisc/log.c and friends. * [Bug 826] Fix redefinition of PI. * [Bug 825] ntp_scanner.c needs to #include . * [Bug 824] New parser code has some build problems with the SIM code. diff --git a/include/isc/dir.h b/include/isc/dir.h new file mode 100644 index 000000000..cc8570643 --- /dev/null +++ b/include/isc/dir.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2004, 2005 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 1999-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: dir.h,v 1.17.18.2 2005/04/29 00:17:09 marka Exp $ */ + +/* Principal Authors: DCL */ + +#ifndef ISC_DIR_H +#define ISC_DIR_H 1 + +/*! \file */ + +#include /* Required on some systems. */ +#include + +#include +#include + +#define ISC_DIR_NAMEMAX 256 +#define ISC_DIR_PATHMAX 1024 + +/*% Directory Entry */ +typedef struct isc_direntry { + /*! + * Ideally, this should be NAME_MAX, but AIX does not define it by + * default and dynamically allocating the space based on pathconf() + * complicates things undesirably, as does adding special conditionals + * just for AIX. So a comfortably sized buffer is chosen instead. + */ + char name[ISC_DIR_NAMEMAX]; + unsigned int length; +} isc_direntry_t; + +/*% Directory */ +typedef struct isc_dir { + unsigned int magic; + /*! + * As with isc_direntry_t->name, making this "right" for all systems + * is slightly problematic because AIX does not define PATH_MAX. + */ + char dirname[ISC_DIR_PATHMAX]; + isc_direntry_t entry; + DIR * handle; +} isc_dir_t; + +ISC_LANG_BEGINDECLS + +void +isc_dir_init(isc_dir_t *dir); + +isc_result_t +isc_dir_open(isc_dir_t *dir, const char *dirname); + +isc_result_t +isc_dir_read(isc_dir_t *dir); + +isc_result_t +isc_dir_reset(isc_dir_t *dir); + +void +isc_dir_close(isc_dir_t *dir); + +isc_result_t +isc_dir_chdir(const char *dirname); + +isc_result_t +isc_dir_chroot(const char *dirname); + +isc_result_t +isc_dir_createunique(char *templet); +/*!< + * Use a templet (such as from isc_file_mktemplate()) to create a uniquely + * named, empty directory. The templet string is modified in place. + * If result == ISC_R_SUCCESS, it is the name of the directory that was + * created. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_DIR_H */ diff --git a/include/isc/file.h b/include/isc/file.h new file mode 100644 index 000000000..16b007572 --- /dev/null +++ b/include/isc/file.h @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2004, 2005 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2000, 2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: file.h,v 1.27.18.2 2005/04/29 00:16:54 marka Exp $ */ + +#ifndef ISC_FILE_H +#define ISC_FILE_H 1 + +/*! \file */ + +#include + +#include +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_file_settime(const char *file, isc_time_t *time); + +isc_result_t +isc_file_getmodtime(const char *file, isc_time_t *time); +/*!< + * \brief Get the time of last modication of a file. + * + * Notes: + *\li The time that is set is relative to the (OS-specific) epoch, as are + * all isc_time_t structures. + * + * Requires: + *\li file != NULL. + *\li time != NULL. + * + * Ensures: + *\li If the file could not be accessed, 'time' is unchanged. + * + * Returns: + *\li #ISC_R_SUCCESS + * Success. + *\li #ISC_R_NOTFOUND + * No such file exists. + *\li #ISC_R_INVALIDFILE + * The path specified was not usable by the operating system. + *\li #ISC_R_NOPERM + * The file's metainformation could not be retrieved because + * permission was denied to some part of the file's path. + *\li #ISC_R_EIO + * Hardware error interacting with the filesystem. + *\li #ISC_R_UNEXPECTED + * Something totally unexpected happened. + * + */ + +isc_result_t +isc_file_mktemplate(const char *path, char *buf, size_t buflen); +/*!< + * \brief Generate a template string suitable for use with isc_file_openunique(). + * + * Notes: + *\li This function is intended to make creating temporary files + * portable between different operating systems. + * + *\li The path is prepended to an implementation-defined string and + * placed into buf. The string has no path characters in it, + * and its maximum length is 14 characters plus a NUL. Thus + * buflen should be at least strlen(path) + 15 characters or + * an error will be returned. + * + * Requires: + *\li buf != NULL. + * + * Ensures: + *\li If result == #ISC_R_SUCCESS: + * buf contains a string suitable for use as the template argument + * to isc_file_openunique(). + * + *\li If result != #ISC_R_SUCCESS: + * buf is unchanged. + * + * Returns: + *\li #ISC_R_SUCCESS Success. + *\li #ISC_R_NOSPACE buflen indicates buf is too small for the catenation + * of the path with the internal template string. + */ + + +isc_result_t +isc_file_openunique(char *templet, FILE **fp); +/*!< + * \brief Create and open a file with a unique name based on 'templet'. + * + * Notes: + *\li 'template' is a reserved work in C++. If you want to complain + * about the spelling of 'templet', first look it up in the + * Merriam-Webster English dictionary. (http://www.m-w.com/) + * + *\li This function works by using the template to generate file names. + * The template must be a writable string, as it is modified in place. + * Trailing X characters in the file name (full file name on Unix, + * basename on Win32 -- eg, tmp-XXXXXX vs XXXXXX.tmp, respectively) + * are replaced with ASCII characters until a non-existent filename + * is found. If the template does not include pathname information, + * the files in the working directory of the program are searched. + * + *\li isc_file_mktemplate is a good, portable way to get a template. + * + * Requires: + *\li 'fp' is non-NULL and '*fp' is NULL. + * + *\li 'template' is non-NULL, and of a form suitable for use by + * the system as described above. + * + * Ensures: + *\li If result is #ISC_R_SUCCESS: + * *fp points to an stream opening in stdio's "w+" mode. + * + *\li If result is not #ISC_R_SUCCESS: + * *fp is NULL. + * + * No file is open. Even if one was created (but unable + * to be reopened as a stdio FILE pointer) then it has been + * removed. + * + *\li This function does *not* ensure that the template string has not been + * modified, even if the operation was unsuccessful. + * + * Returns: + *\li #ISC_R_SUCCESS + * Success. + *\li #ISC_R_EXISTS + * No file with a unique name could be created based on the + * template. + *\li #ISC_R_INVALIDFILE + * The path specified was not usable by the operating system. + *\li #ISC_R_NOPERM + * The file could not be created because permission was denied + * to some part of the file's path. + *\li #ISC_R_IOERROR + * Hardware error interacting with the filesystem. + *\li #ISC_R_UNEXPECTED + * Something totally unexpected happened. + */ + +isc_result_t +isc_file_remove(const char *filename); +/*!< + * \brief Remove the file named by 'filename'. + */ + +isc_result_t +isc_file_rename(const char *oldname, const char *newname); +/*!< + * \brief Rename the file 'oldname' to 'newname'. + */ + +isc_boolean_t +isc_file_exists(const char *pathname); +/*!< + * \brief Return #ISC_TRUE if the calling process can tell that the given file exists. + * Will not return true if the calling process has insufficient privileges + * to search the entire path. + */ + +isc_boolean_t +isc_file_isabsolute(const char *filename); +/*!< + * \brief Return #ISC_TRUE if the given file name is absolute. + */ + +isc_boolean_t +isc_file_iscurrentdir(const char *filename); +/*!< + * \brief Return #ISC_TRUE if the given file name is the current directory ("."). + */ + +isc_boolean_t +isc_file_ischdiridempotent(const char *filename); +/*%< + * Return #ISC_TRUE if calling chdir(filename) multiple times will give + * the same result as calling it once. + */ + +const char * +isc_file_basename(const char *filename); +/*%< + * Return the final component of the path in the file name. + */ + +isc_result_t +isc_file_progname(const char *filename, char *buf, size_t buflen); +/*!< + * \brief Given an operating system specific file name "filename" + * referring to a program, return the canonical program name. + * + * + * Any directory prefix or executable file name extension (if + * used on the OS in case) is stripped. On systems where program + * names are case insensitive, the name is canonicalized to all + * lower case. The name is written to 'buf', an array of 'buflen' + * chars, and null terminated. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOSPACE The name did not fit in 'buf'. + */ + +isc_result_t +isc_file_template(const char *path, const char *templet, char *buf, + size_t buflen); +/*%< + * Create an OS specific template using 'path' to define the directory + * 'templet' to describe the filename and store the result in 'buf' + * such that path can be renamed to buf atomically. + */ + +isc_result_t +isc_file_renameunique(const char *file, char *templet); +/*%< + * Rename 'file' using 'templet' as a template for the new file name. + */ + +isc_result_t +isc_file_absolutepath(const char *filename, char *path, size_t pathlen); +/*%< + * Given a file name, return the fully qualified path to the file. + */ + +/* + * XXX We should also have a isc_file_writeeopen() function + * for safely open a file in a publicly writable directory + * (see write_open() in BIND 8's ns_config.c). + */ + +isc_result_t +isc_file_truncate(const char *filename, isc_offset_t size); +/*%< + * Truncate/extend the file specified to 'size' bytes. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_FILE_H */ diff --git a/include/isc/log.h b/include/isc/log.h new file mode 100644 index 000000000..c3817758f --- /dev/null +++ b/include/isc/log.h @@ -0,0 +1,913 @@ +/* + * Copyright (C) 2004, 2005 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 1999-2002 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: log.h,v 1.47.18.3 2005/04/29 00:16:58 marka Exp $ */ + +#ifndef ISC_LOG_H +#define ISC_LOG_H 1 + +/*! \file */ + +#include +#include +#include /* XXXDCL NT */ + +#include +#include +#include +#include + +/*@{*/ +/*! + * \brief Severity levels, patterned after Unix's syslog levels. + * + */ +#define ISC_LOG_DEBUG(level) (level) +/*! + * #ISC_LOG_DYNAMIC can only be used for defining channels with + * isc_log_createchannel(), not to specify a level in isc_log_write(). + */ +#define ISC_LOG_DYNAMIC 0 +#define ISC_LOG_INFO (-1) +#define ISC_LOG_NOTICE (-2) +#define ISC_LOG_WARNING (-3) +#define ISC_LOG_ERROR (-4) +#define ISC_LOG_CRITICAL (-5) +/*@}*/ + +/*@{*/ +/*! + * \brief Destinations. + */ +#define ISC_LOG_TONULL 1 +#define ISC_LOG_TOSYSLOG 2 +#define ISC_LOG_TOFILE 3 +#define ISC_LOG_TOFILEDESC 4 +/*@}*/ + +/*@{*/ +/*% + * Channel flags. + */ +#define ISC_LOG_PRINTTIME 0x0001 +#define ISC_LOG_PRINTLEVEL 0x0002 +#define ISC_LOG_PRINTCATEGORY 0x0004 +#define ISC_LOG_PRINTMODULE 0x0008 +#define ISC_LOG_PRINTTAG 0x0010 +#define ISC_LOG_PRINTALL 0x001F +#define ISC_LOG_DEBUGONLY 0x1000 +#define ISC_LOG_OPENERR 0x8000 /* internal */ +/*@}*/ + +/*@{*/ +/*! + * \brief Other options. + * + * XXXDCL INFINITE doesn't yet work. Arguably it isn't needed, but + * since I am intend to make large number of versions work efficiently, + * INFINITE is going to be trivial to add to that. + */ +#define ISC_LOG_ROLLINFINITE (-1) +#define ISC_LOG_ROLLNEVER (-2) +/*@}*/ + +/*! + * \brief Used to name the categories used by a library. + * + * An array of isc_logcategory + * structures names each category, and the id value is initialized by calling + * isc_log_registercategories. + */ +struct isc_logcategory { + const char *name; + unsigned int id; +}; + +/*% + * Similar to isc_logcategory, but for all the modules a library defines. + */ +struct isc_logmodule { + const char *name; + unsigned int id; +}; + +/*% + * The isc_logfile structure is initialized as part of an isc_logdestination + * before calling isc_log_createchannel(). + * + * When defining an #ISC_LOG_TOFILE + * channel the name, versions and maximum_size should be set before calling + * isc_log_createchannel(). To define an #ISC_LOG_TOFILEDESC channel set only + * the stream before the call. + * + * Setting maximum_size to zero implies no maximum. + */ +typedef struct isc_logfile { + FILE *stream; /*%< Initialized to NULL for #ISC_LOG_TOFILE. */ + const char *name; /*%< NULL for #ISC_LOG_TOFILEDESC. */ + int versions; /* >= 0, #ISC_LOG_ROLLNEVER, #ISC_LOG_ROLLINFINITE. */ + /*% + * stdio's ftell is standardized to return a long, which may well not + * be big enough for the largest file supportable by the operating + * system (though it is _probably_ big enough for the largest log + * anyone would want). st_size returned by fstat should be typedef'd + * to a size large enough for the largest possible file on a system. + */ + isc_offset_t maximum_size; + isc_boolean_t maximum_reached; /*%< Private. */ +} isc_logfile_t; + +/*% + * Passed to isc_log_createchannel to define the attributes of either + * a stdio or a syslog log. + */ +typedef union isc_logdestination { + isc_logfile_t file; + int facility; /* XXXDCL NT */ +} isc_logdestination_t; + +/*@{*/ +/*% + * The built-in categories of libisc. + * + * Each library registering categories should provide library_LOGCATEGORY_name + * definitions with indexes into its isc_logcategory structure corresponding to + * the order of the names. + */ +LIBISC_EXTERNAL_DATA extern isc_logcategory_t isc_categories[]; +LIBISC_EXTERNAL_DATA extern isc_log_t *isc_lctx; +LIBISC_EXTERNAL_DATA extern isc_logmodule_t isc_modules[]; +/*@}*/ + +/*@{*/ +/*% + * Do not log directly to DEFAULT. Use another category. When in doubt, + * use GENERAL. + */ +#define ISC_LOGCATEGORY_DEFAULT (&isc_categories[0]) +#define ISC_LOGCATEGORY_GENERAL (&isc_categories[1]) +/*@}*/ + +#define ISC_LOGMODULE_SOCKET (&isc_modules[0]) +#define ISC_LOGMODULE_TIME (&isc_modules[1]) +#define ISC_LOGMODULE_INTERFACE (&isc_modules[2]) +#define ISC_LOGMODULE_TIMER (&isc_modules[3]) + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_log_create(isc_mem_t *mctx, isc_log_t **lctxp, isc_logconfig_t **lcfgp); +/*%< + * Establish a new logging context, with default channels. + * + * Notes: + *\li isc_log_create() calls isc_logconfig_create(), so see its comment + * below for more information. + * + * Requires: + *\li mctx is a valid memory context. + *\li lctxp is not null and *lctxp is null. + *\li lcfgp is null or lcfgp is not null and *lcfgp is null. + * + * Ensures: + *\li *lctxp will point to a valid logging context if all of the necessary + * memory was allocated, or NULL otherwise. + *\li *lcfgp will point to a valid logging configuration if all of the + * necessary memory was allocated, or NULL otherwise. + *\li On failure, no additional memory is allocated. + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_NOMEMORY Resource limit: Out of memory + */ + +isc_result_t +isc_logconfig_create(isc_log_t *lctx, isc_logconfig_t **lcfgp); +/*%< + * Create the data structure that holds all of the configurable information + * about where messages are actually supposed to be sent -- the information + * that could changed based on some configuration file, as opposed to the + * the category/module specification of isc_log_[v]write[1] that is compiled + * into a program, or the debug_level which is dynamic state information. + * + * Notes: + *\li It is necessary to specify the logging context the configuration + * will be used with because the number of categories and modules + * needs to be known in order to set the configuration. However, + * the configuration is not used by the logging context until the + * isc_logconfig_use function is called. + * + *\li The memory context used for operations that allocate memory for + * the configuration is that of the logging context, as specified + * in the isc_log_create call. + * + *\li Four default channels are established: + *\verbatim + * default_syslog + * - log to syslog's daemon facility #ISC_LOG_INFO or higher + * default_stderr + * - log to stderr #ISC_LOG_INFO or higher + * default_debug + * - log to stderr #ISC_LOG_DEBUG dynamically + * null + * - log nothing + *\endverbatim + * + * Requires: + *\li lctx is a valid logging context. + *\li lcftp is not null and *lcfgp is null. + * + * Ensures: + *\li *lcfgp will point to a valid logging context if all of the necessary + * memory was allocated, or NULL otherwise. + *\li On failure, no additional memory is allocated. + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_NOMEMORY Resource limit: Out of memory + */ + +isc_logconfig_t * +isc_logconfig_get(isc_log_t *lctx); +/*%< + * Returns a pointer to the configuration currently in use by the log context. + * + * Requires: + *\li lctx is a valid context. + * + * Ensures: + *\li The configuration pointer is non-null. + * + * Returns: + *\li The configuration pointer. + */ + +isc_result_t +isc_logconfig_use(isc_log_t *lctx, isc_logconfig_t *lcfg); +/*%< + * Associate a new configuration with a logging context. + * + * Notes: + *\li This is thread safe. The logging context will lock a mutex + * before attempting to swap in the new configuration, and isc_log_doit + * (the internal function used by all of isc_log_[v]write[1]) locks + * the same lock for the duration of its use of the configuration. + * + * Requires: + *\li lctx is a valid logging context. + *\li lcfg is a valid logging configuration. + *\li lctx is the same configuration given to isc_logconfig_create + * when the configuration was created. + * + * Ensures: + *\li Future calls to isc_log_write will use the new configuration. + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_NOMEMORY Resource limit: Out of memory + */ + +void +isc_log_destroy(isc_log_t **lctxp); +/*%< + * Deallocate the memory associated with a logging context. + * + * Requires: + *\li *lctx is a valid logging context. + * + * Ensures: + *\li All of the memory associated with the logging context is returned + * to the free memory pool. + * + *\li Any open files are closed. + * + *\li The logging context is marked as invalid. + */ + +void +isc_logconfig_destroy(isc_logconfig_t **lcfgp); +/*%< + * Destroy a logging configuration. + * + * Notes: + *\li This function cannot be used directly with the return value of + * isc_logconfig_get, because a logging context must always have + * a valid configuration associated with it. + * + * Requires: + *\li lcfgp is not null and *lcfgp is a valid logging configuration. + *\li The logging configuration is not in use by an existing logging context. + * + * Ensures: + *\li All memory allocated for the configuration is freed. + * + *\li The configuration is marked as invalid. + */ + +void +isc_log_registercategories(isc_log_t *lctx, isc_logcategory_t categories[]); +/*%< + * Identify logging categories a library will use. + * + * Notes: + *\li A category should only be registered once, but no mechanism enforces + * this rule. + * + *\li The end of the categories array is identified by a NULL name. + * + *\li Because the name is used by #ISC_LOG_PRINTCATEGORY, it should not + * be altered or destroyed after isc_log_registercategories(). + * + *\li Because each element of the categories array is used by + * isc_log_categorybyname, it should not be altered or destroyed + * after registration. + * + *\li The value of the id integer in each structure is overwritten + * by this function, and so id need not be initialized to any particular + * value prior to the function call. + * + *\li A subsequent call to isc_log_registercategories with the same + * logging context (but new categories) will cause the last + * element of the categories array from the prior call to have + * its "name" member changed from NULL to point to the new + * categories array, and its "id" member set to UINT_MAX. + * + * Requires: + *\li lctx is a valid logging context. + *\li categories != NULL. + *\li categories[0].name != NULL. + * + * Ensures: + * \li There are references to each category in the logging context, + * so they can be used with isc_log_usechannel() and isc_log_write(). + */ + +void +isc_log_registermodules(isc_log_t *lctx, isc_logmodule_t modules[]); +/*%< + * Identify logging categories a library will use. + * + * Notes: + *\li A module should only be registered once, but no mechanism enforces + * this rule. + * + *\li The end of the modules array is identified by a NULL name. + * + *\li Because the name is used by #ISC_LOG_PRINTMODULE, it should not + * be altered or destroyed after isc_log_registermodules(). + * + *\li Because each element of the modules array is used by + * isc_log_modulebyname, it should not be altered or destroyed + * after registration. + * + *\li The value of the id integer in each structure is overwritten + * by this function, and so id need not be initialized to any particular + * value prior to the function call. + * + *\li A subsequent call to isc_log_registermodules with the same + * logging context (but new modules) will cause the last + * element of the modules array from the prior call to have + * its "name" member changed from NULL to point to the new + * modules array, and its "id" member set to UINT_MAX. + * + * Requires: + *\li lctx is a valid logging context. + *\li modules != NULL. + *\li modules[0].name != NULL; + * + * Ensures: + *\li Each module has a reference in the logging context, so they can be + * used with isc_log_usechannel() and isc_log_write(). + */ + +isc_result_t +isc_log_createchannel(isc_logconfig_t *lcfg, const char *name, + unsigned int type, int level, + const isc_logdestination_t *destination, + unsigned int flags); +/*%< + * Specify the parameters of a logging channel. + * + * Notes: + *\li The name argument is copied to memory in the logging context, so + * it can be altered or destroyed after isc_log_createchannel(). + * + *\li Defining a very large number of channels will have a performance + * impact on isc_log_usechannel(), since the names are searched + * linearly until a match is made. This same issue does not affect + * isc_log_write, however. + * + *\li Channel names can be redefined; this is primarily useful for programs + * that want their own definition of default_syslog, default_debug + * and default_stderr. + * + *\li Any channel that is redefined will not affect logging that was + * already directed to its original definition, _except_ for the + * default_stderr channel. This case is handled specially so that + * the default logging category can be changed by redefining + * default_stderr. (XXXDCL Though now that I think of it, the default + * logging category can be changed with only one additional function + * call by defining a new channel and then calling isc_log_usechannel() + * for #ISC_LOGCATEGORY_DEFAULT.) + * + *\li Specifying #ISC_LOG_PRINTTIME or #ISC_LOG_PRINTTAG for syslog is allowed, + * but probably not what you wanted to do. + * + * #ISC_LOG_DEBUGONLY will mark the channel as usable only when the + * debug level of the logging context (see isc_log_setdebuglevel) + * is non-zero. + * + * Requires: + *\li lcfg is a valid logging configuration. + * + *\li name is not NULL. + * + *\li type is #ISC_LOG_TOSYSLOG, #ISC_LOG_TOFILE, #ISC_LOG_TOFILEDESC or + * #ISC_LOG_TONULL. + * + *\li destination is not NULL unless type is #ISC_LOG_TONULL. + * + *\li level is >= #ISC_LOG_CRITICAL (the most negative logging level). + * + *\li flags does not include any bits aside from the ISC_LOG_PRINT* bits + * or #ISC_LOG_DEBUGONLY. + * + * Ensures: + *\li #ISC_R_SUCCESS + * A channel with the given name is usable with + * isc_log_usechannel(). + * + *\li #ISC_R_NOMEMORY or #ISC_R_UNEXPECTED + * No additional memory is being used by the logging context. + * Any channel that previously existed with the given name + * is not redefined. + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_NOMEMORY Resource limit: Out of memory + *\li #ISC_R_UNEXPECTED type was out of range and REQUIRE() + * was disabled. + */ + +isc_result_t +isc_log_usechannel(isc_logconfig_t *lcfg, const char *name, + const isc_logcategory_t *category, + const isc_logmodule_t *module); +/*%< + * Associate a named logging channel with a category and module that + * will use it. + * + * Notes: + *\li The name is searched for linearly in the set of known channel names + * until a match is found. (Note the performance impact of a very large + * number of named channels.) When multiple channels of the same + * name are defined, the most recent definition is found. + * + *\li Specifing a very large number of channels for a category will have + * a moderate impact on performance in isc_log_write(), as each + * call looks up the category for the start of a linked list, which + * it follows all the way to the end to find matching modules. The + * test for matching modules is integral, though. + * + *\li If category is NULL, then the channel is associated with the indicated + * module for all known categories (including the "default" category). + * + *\li If module is NULL, then the channel is associated with every module + * that uses that category. + * + *\li Passing both category and module as NULL would make every log message + * use the indicated channel. + * + * \li Specifying a channel that is #ISC_LOG_TONULL for a category/module pair + * has no effect on any other channels associated with that pair, + * regardless of ordering. Thus you cannot use it to "mask out" one + * category/module pair when you have specified some other channel that + * is also used by that category/module pair. + * + * Requires: + *\li lcfg is a valid logging configuration. + * + *\li category is NULL or has an id that is in the range of known ids. + * + * module is NULL or has an id that is in the range of known ids. + * + * Ensures: + *\li #ISC_R_SUCCESS + * The channel will be used by the indicated category/module + * arguments. + * + *\li #ISC_R_NOMEMORY + * If assignment for a specific category has been requested, + * the channel has not been associated with the indicated + * category/module arguments and no additional memory is + * used by the logging context. + * If assignment for all categories has been requested + * then _some_ may have succeeded (starting with category + * "default" and progressing through the order of categories + * passed to isc_log_registercategories()) and additional memory + * is being used by whatever assignments succeeded. + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_NOMEMORY Resource limit: Out of memory + */ + +/* Attention: next four comments PRECEED code */ +/*! + * \brief + * Write a message to the log channels. + * + * Notes: + *\li Log messages containing natural language text should be logged with + * isc_log_iwrite() to allow for localization. + * + *\li lctx can be NULL; this is allowed so that programs which use + * libraries that use the ISC logging system are not required to + * also use it. + * + *\li The format argument is a printf(3) string, with additional arguments + * as necessary. + * + * Requires: + *\li lctx is a valid logging context. + * + *\li The category and module arguments must have ids that are in the + * range of known ids, as estabished by isc_log_registercategories() + * and isc_log_registermodules(). + * + *\li level != #ISC_LOG_DYNAMIC. ISC_LOG_DYNAMIC is used only to define + * channels, and explicit debugging level must be identified for + * isc_log_write() via ISC_LOG_DEBUG(level). + * + *\li format != NULL. + * + * Ensures: + *\li The log message is written to every channel associated with the + * indicated category/module pair. + * + * Returns: + *\li Nothing. Failure to log a message is not construed as a + * meaningful error. + */ +void +isc_log_write(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, + const char *format, ...) + +ISC_FORMAT_PRINTF(5, 6); + +/*% + * Write a message to the log channels. + * + * Notes: + *\li lctx can be NULL; this is allowed so that programs which use + * libraries that use the ISC logging system are not required to + * also use it. + * + *\li The format argument is a printf(3) string, with additional arguments + * as necessary. + * + * Requires: + *\li lctx is a valid logging context. + * + *\li The category and module arguments must have ids that are in the + * range of known ids, as estabished by isc_log_registercategories() + * and isc_log_registermodules(). + * + *\li level != #ISC_LOG_DYNAMIC. ISC_LOG_DYNAMIC is used only to define + * channels, and explicit debugging level must be identified for + * isc_log_write() via ISC_LOG_DEBUG(level). + * + *\li format != NULL. + * + * Ensures: + *\li The log message is written to every channel associated with the + * indicated category/module pair. + * + * Returns: + *\li Nothing. Failure to log a message is not construed as a + * meaningful error. + */ +void +isc_log_vwrite(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, + const char *format, va_list args) + +ISC_FORMAT_PRINTF(5, 0); + +/*% + * Write a message to the log channels, pruning duplicates that occur within + * a configurable amount of seconds (see isc_log_[sg]etduplicateinterval). + * This function is otherwise identical to isc_log_write(). + */ +void +isc_log_write1(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *format, ...) + +ISC_FORMAT_PRINTF(5, 6); + +/*% + * Write a message to the log channels, pruning duplicates that occur within + * a configurable amount of seconds (see isc_log_[sg]etduplicateinterval). + * This function is otherwise identical to isc_log_vwrite(). + */ +void +isc_log_vwrite1(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *format, + va_list args) + +ISC_FORMAT_PRINTF(5, 0); + +/*% + * These are four internationalized versions of the the isc_log_[v]write[1] + * functions. + * + * The only difference is that they take arguments for a message + * catalog, message set, and message number, all immediately preceding the + * format argument. The format argument becomes the default text, a la + * isc_msgcat_get. If the message catalog is NULL, no lookup is attempted + * for a message -- which makes the message set and message number irrelevant, + * and the non-internationalized call should have probably been used instead. + * + * Yes, that means there are now *eight* interfaces to logging a message. + * Sheesh. Make the madness stop! + */ +/*@{*/ +void +isc_log_iwrite(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, + isc_msgcat_t *msgcat, int msgset, int message, + const char *format, ...) +ISC_FORMAT_PRINTF(8, 9); + +void +isc_log_ivwrite(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, + isc_msgcat_t *msgcat, int msgset, int message, + const char *format, va_list args) +ISC_FORMAT_PRINTF(8, 0); + +void +isc_log_iwrite1(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, + isc_msgcat_t *msgcat, int msgset, int message, + const char *format, ...) +ISC_FORMAT_PRINTF(8, 9); + +void +isc_log_ivwrite1(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, + isc_msgcat_t *msgcat, int msgset, int message, + const char *format, va_list args) +ISC_FORMAT_PRINTF(8, 0); +/*@}*/ + +void +isc_log_setdebuglevel(isc_log_t *lctx, unsigned int level); +/*%< + * Set the debugging level used for logging. + * + * Notes: + *\li Setting the debugging level to 0 disables debugging log messages. + * + * Requires: + *\li lctx is a valid logging context. + * + * Ensures: + *\li The debugging level is set to the requested value. + */ + +unsigned int +isc_log_getdebuglevel(isc_log_t *lctx); +/*%< + * Get the current debugging level. + * + * Notes: + *\li This is provided so that a program can have a notion of + * "increment debugging level" or "decrement debugging level" + * without needing to keep track of what the current level is. + * + *\li A return value of 0 indicates that debugging messages are disabled. + * + * Requires: + *\li lctx is a valid logging context. + * + * Ensures: + *\li The current logging debugging level is returned. + */ + +isc_boolean_t +isc_log_wouldlog(isc_log_t *lctx, int level); +/*%< + * Determine whether logging something to 'lctx' at 'level' would + * actually cause something to be logged somewhere. + * + * If #ISC_FALSE is returned, it is guaranteed that nothing would + * be logged, allowing the caller to omit unnecessary + * isc_log_write() calls and possible message preformatting. + */ + +void +isc_log_setduplicateinterval(isc_logconfig_t *lcfg, unsigned int interval); +/*%< + * Set the interval over which duplicate log messages will be ignored + * by isc_log_[v]write1(), in seconds. + * + * Notes: + *\li Increasing the duplicate interval from X to Y will not necessarily + * filter out duplicates of messages logged in Y - X seconds since the + * increase. (Example: Message1 is logged at midnight. Message2 + * is logged at 00:01:00, when the interval is only 30 seconds, causing + * Message1 to be expired from the log message history. Then the interval + * is increased to 3000 (five minutes) and at 00:04:00 Message1 is logged + * again. It will appear the second time even though less than five + * passed since the first occurrence. + * + * Requires: + *\li lctx is a valid logging context. + */ + +unsigned int +isc_log_getduplicateinterval(isc_logconfig_t *lcfg); +/*%< + * Get the current duplicate filtering interval. + * + * Requires: + *\li lctx is a valid logging context. + * + * Returns: + *\li The current duplicate filtering interval. + */ + +isc_result_t +isc_log_settag(isc_logconfig_t *lcfg, const char *tag); +/*%< + * Set the program name or other identifier for #ISC_LOG_PRINTTAG. + * + * Requires: + *\li lcfg is a valid logging configuration. + * + * Notes: + *\li If this function has not set the tag to a non-NULL, non-empty value, + * then the #ISC_LOG_PRINTTAG channel flag will not print anything. + * Unlike some implementations of syslog on Unix systems, you *must* set + * the tag in order to get it logged. It is not implicitly derived from + * the program name (which is pretty impossible to infer portably). + * + *\li Setting the tag to NULL or the empty string will also cause the + * #ISC_LOG_PRINTTAG channel flag to not print anything. If tag equals the + * empty string, calls to isc_log_gettag will return NULL. + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_NOMEMORY Resource Limit: Out of memory + * + * XXXDCL when creating a new isc_logconfig_t, it might be nice if the tag + * of the currently active isc_logconfig_t was inherited. this does not + * currently happen. + */ + +char * +isc_log_gettag(isc_logconfig_t *lcfg); +/*%< + * Get the current identifier printed with #ISC_LOG_PRINTTAG. + * + * Requires: + *\li lcfg is a valid logging configuration. + * + * Notes: + *\li Since isc_log_settag() will not associate a zero-length string + * with the logging configuration, attempts to do so will cause + * this function to return NULL. However, a determined programmer + * will observe that (currently) a tag of length greater than zero + * could be set, and then modified to be zero length. + * + * Returns: + *\li A pointer to the current identifier, or NULL if none has been set. + */ + +void +isc_log_opensyslog(const char *tag, int options, int facility); +/*%< + * Initialize syslog logging. + * + * Notes: + *\li XXXDCL NT + * This is currently equivalent to openlog(), but is not going to remain + * that way. In the meantime, the arguments are all identical to + * those used by openlog(3), as follows: + * + * \code + * tag: The string to use in the position of the program + * name in syslog messages. Most (all?) syslogs + * will use basename(argv[0]) if tag is NULL. + * + * options: LOG_CONS, LOG_PID, LOG_NDELAY ... whatever your + * syslog supports. + * + * facility: The default syslog facility. This is irrelevant + * since isc_log_write will ALWAYS use the channel's + * declared facility. + * \endcode + * + *\li Zero effort has been made (yet) to accomodate systems with openlog() + * that only takes two arguments, or to identify valid syslog + * facilities or options for any given architecture. + * + *\li It is necessary to call isc_log_opensyslog() to initialize + * syslogging on machines which do not support network connections to + * syslogd because they require a Unix domain socket to be used. Since + * this is a chore to determine at run-time, it is suggested that it + * always be called by programs using the ISC logging system. + * + * Requires: + *\li Nothing. + * + * Ensures: + *\li openlog() is called to initialize the syslog system. + */ + +void +isc_log_closefilelogs(isc_log_t *lctx); +/*%< + * Close all open files used by #ISC_LOG_TOFILE channels. + * + * Notes: + *\li This function is provided for programs that want to use their own + * log rolling mechanism rather than the one provided internally. + * For example, a program that wanted to keep daily logs would define + * a channel which used #ISC_LOG_ROLLNEVER, then once a day would + * rename the log file and call isc_log_closefilelogs(). + * + *\li #ISC_LOG_TOFILEDESC channels are unaffected. + * + * Requires: + *\li lctx is a valid context. + * + * Ensures: + *\li The open files are closed and will be reopened when they are + * next needed. + */ + +isc_logcategory_t * +isc_log_categorybyname(isc_log_t *lctx, const char *name); +/*%< + * Find a category by its name. + * + * Notes: + *\li The string name of a category is not required to be unique. + * + * Requires: + *\li lctx is a valid context. + *\li name is not NULL. + * + * Returns: + *\li A pointer to the _first_ isc_logcategory_t structure used by "name". + * + *\li NULL if no category exists by that name. + */ + +isc_logmodule_t * +isc_log_modulebyname(isc_log_t *lctx, const char *name); +/*%< + * Find a module by its name. + * + * Notes: + *\li The string name of a module is not required to be unique. + * + * Requires: + *\li lctx is a valid context. + *\li name is not NULL. + * + * Returns: + *\li A pointer to the _first_ isc_logmodule_t structure used by "name". + * + *\li NULL if no module exists by that name. + */ + +void +isc_log_setcontext(isc_log_t *lctx); +/*%< + * Sets the context used by the libisc for logging. + * + * Requires: + *\li lctx be a valid context. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_LOG_H */ diff --git a/include/isc/stat.h b/include/isc/stat.h new file mode 100644 index 000000000..d1b248919 --- /dev/null +++ b/include/isc/stat.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2004 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: stat.h,v 1.2.18.1 2004/08/19 04:42:54 marka Exp $ */ + +#ifndef ISC_STAT_H +#define ISC_STAT_H 1 + +/***** + ***** Module Info + *****/ + +/* + * Portable netdb.h support. + * + * This module is responsible for defining S_IS??? macros. + * + * MP: + * No impact. + * + * Reliability: + * No anticipated impact. + * + * Resources: + * N/A. + * + * Security: + * No anticipated impact. + * + */ + +/*** + *** Imports. + ***/ + +#include +#include + +#endif /* ISC_STAT_H */ diff --git a/include/isc/stdio.h b/include/isc/stdio.h new file mode 100644 index 000000000..e3bf0cd3e --- /dev/null +++ b/include/isc/stdio.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2004, 2005 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2000, 2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: stdio.h,v 1.7.18.2 2005/04/29 00:17:03 marka Exp $ */ + +#ifndef ISC_STDIO_H +#define ISC_STDIO_H 1 + +/*! \file */ + +/*% + * These functions are wrappers around the corresponding stdio functions. + * + * They return a detailed error code in the form of an an isc_result_t. ANSI C + * does not guarantee that stdio functions set errno, hence these functions + * must use platform dependent methods (e.g., the POSIX errno) to construct the + * error code. + */ + +#include + +#include +#include + +ISC_LANG_BEGINDECLS + +/*% Open */ +isc_result_t +isc_stdio_open(const char *filename, const char *mode, FILE **fp); + +/*% Close */ +isc_result_t +isc_stdio_close(FILE *f); + +/*% Seek */ +isc_result_t +isc_stdio_seek(FILE *f, long offset, int whence); + +/*% Read */ +isc_result_t +isc_stdio_read(void *ptr, size_t size, size_t nmemb, FILE *f, + size_t *nret); + +/*% Write */ +isc_result_t +isc_stdio_write(const void *ptr, size_t size, size_t nmemb, FILE *f, + size_t *nret); + +/*% Flush */ +isc_result_t +isc_stdio_flush(FILE *f); + +isc_result_t +isc_stdio_sync(FILE *f); +/*%< + * Invoke fsync() on the file descriptor underlying an stdio stream, or an + * equivalent system-dependent operation. Note that this function has no + * direct counterpart in the stdio library. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_STDIO_H */ diff --git a/include/isc/time.h b/include/isc/time.h new file mode 100644 index 000000000..65794392a --- /dev/null +++ b/include/isc/time.h @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2004, 2005 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 1998-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: time.h,v 1.30.18.2 2005/04/29 00:17:10 marka Exp $ */ + +#ifndef ISC_TIME_H +#define ISC_TIME_H 1 + +/*! \file */ + +#include +#include + +/*** + *** Intervals + ***/ + +/*! + * \brief + * The contents of this structure are private, and MUST NOT be accessed + * directly by callers. + * + * The contents are exposed only to allow callers to avoid dynamic allocation. + */ +struct isc_interval { + unsigned int seconds; + unsigned int nanoseconds; +}; + +extern isc_interval_t *isc_interval_zero; + +ISC_LANG_BEGINDECLS + +void +isc_interval_set(isc_interval_t *i, + unsigned int seconds, unsigned int nanoseconds); +/*%< + * Set 'i' to a value representing an interval of 'seconds' seconds and + * 'nanoseconds' nanoseconds, suitable for use in isc_time_add() and + * isc_time_subtract(). + * + * Requires: + * + *\li 't' is a valid pointer. + *\li nanoseconds < 1000000000. + */ + +isc_boolean_t +isc_interval_iszero(const isc_interval_t *i); +/*%< + * Returns ISC_TRUE iff. 'i' is the zero interval. + * + * Requires: + * + *\li 'i' is a valid pointer. + */ + +/*** + *** Absolute Times + ***/ + +/*% + * The contents of this structure are private, and MUST NOT be accessed + * directly by callers. + * + * The contents are exposed only to allow callers to avoid dynamic allocation. + */ + +struct isc_time { + unsigned int seconds; + unsigned int nanoseconds; +}; + +extern isc_time_t *isc_time_epoch; + +void +isc_time_set(isc_time_t *t, unsigned int seconds, unsigned int nanoseconds); +/*%< + * Set 't' to a particular number of seconds + nanoseconds since the epoch. + * + * Notes: + *\li This call is equivalent to: + *\code + * isc_time_settoepoch(t); + * isc_interval_set(i, seconds, nanoseconds); + * isc_time_add(t, i, t); + *\endcode + * Requires: + *\li 't' is a valid pointer. + *\li nanoseconds < 1000000000. + */ + +void +isc_time_settoepoch(isc_time_t *t); +/*%< + * Set 't' to the time of the epoch. + * + * Notes: + * \li The date of the epoch is platform-dependent. + * + * Requires: + * + *\li 't' is a valid pointer. + */ + +isc_boolean_t +isc_time_isepoch(const isc_time_t *t); +/*%< + * Returns ISC_TRUE iff. 't' is the epoch ("time zero"). + * + * Requires: + * + *\li 't' is a valid pointer. + */ + +isc_result_t +isc_time_now(isc_time_t *t); +/*%< + * Set 't' to the current absolute time. + * + * Requires: + * + *\li 't' is a valid pointer. + * + * Returns: + * + *\li Success + *\li Unexpected error + * Getting the time from the system failed. + *\li Out of range + * The time from the system is too large to be represented + * in the current definition of isc_time_t. + */ + +isc_result_t +isc_time_nowplusinterval(isc_time_t *t, const isc_interval_t *i); +/*%< + * Set *t to the current absolute time + i. + * + * Note: + *\li This call is equivalent to: + * + *\code + * isc_time_now(t); + * isc_time_add(t, i, t); + *\endcode + * + * Requires: + * + *\li 't' and 'i' are valid pointers. + * + * Returns: + * + *\li Success + *\li Unexpected error + * Getting the time from the system failed. + *\li Out of range + * The interval added to the time from the system is too large to + * be represented in the current definition of isc_time_t. + */ + +int +isc_time_compare(const isc_time_t *t1, const isc_time_t *t2); +/*%< + * Compare the times referenced by 't1' and 't2' + * + * Requires: + * + *\li 't1' and 't2' are valid pointers. + * + * Returns: + * + *\li -1 t1 < t2 (comparing times, not pointers) + *\li 0 t1 = t2 + *\li 1 t1 > t2 + */ + +isc_result_t +isc_time_add(const isc_time_t *t, const isc_interval_t *i, isc_time_t *result); +/*%< + * Add 'i' to 't', storing the result in 'result'. + * + * Requires: + * + *\li 't', 'i', and 'result' are valid pointers. + * + * Returns: + * \li Success + *\li Out of range + * The interval added to the time is too large to + * be represented in the current definition of isc_time_t. + */ + +isc_result_t +isc_time_subtract(const isc_time_t *t, const isc_interval_t *i, + isc_time_t *result); +/*%< + * Subtract 'i' from 't', storing the result in 'result'. + * + * Requires: + * + *\li 't', 'i', and 'result' are valid pointers. + * + * Returns: + *\li Success + *\li Out of range + * The interval is larger than the time since the epoch. + */ + +isc_uint64_t +isc_time_microdiff(const isc_time_t *t1, const isc_time_t *t2); +/*%< + * Find the difference in microseconds between time t1 and time t2. + * t2 is the subtrahend of t1; ie, difference = t1 - t2. + * + * Requires: + * + *\li 't1' and 't2' are valid pointers. + * + * Returns: + *\li The difference of t1 - t2, or 0 if t1 <= t2. + */ + +isc_uint32_t +isc_time_seconds(const isc_time_t *t); +/*%< + * Return the number of seconds since the epoch stored in a time structure. + * + * Requires: + * + *\li 't' is a valid pointer. + */ + +isc_result_t +isc_time_secondsastimet(const isc_time_t *t, time_t *secondsp); +/*%< + * Ensure the number of seconds in an isc_time_t is representable by a time_t. + * + * Notes: + *\li The number of seconds stored in an isc_time_t might be larger + * than the number of seconds a time_t is able to handle. Since + * time_t is mostly opaque according to the ANSI/ISO standard + * (essentially, all you can be sure of is that it is an arithmetic type, + * not even necessarily integral), it can be tricky to ensure that + * the isc_time_t is in the range a time_t can handle. Use this + * function in place of isc_time_seconds() any time you need to set a + * time_t from an isc_time_t. + * + * Requires: + *\li 't' is a valid pointer. + * + * Returns: + *\li Success + *\li Out of range + */ + +isc_uint32_t +isc_time_nanoseconds(const isc_time_t *t); +/*%< + * Return the number of nanoseconds stored in a time structure. + * + * Notes: + *\li This is the number of nanoseconds in excess of the the number + * of seconds since the epoch; it will always be less than one + * full second. + * + * Requires: + *\li 't' is a valid pointer. + * + * Ensures: + *\li The returned value is less than 1*10^9. + */ + +void +isc_time_formattimestamp(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using a format like "30-Aug-2000 04:06:47.997" and the local time zone. + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + * \li 'buf' points to an array of at least len chars + * + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_TIME_H */ diff --git a/libisc/log.c b/libisc/log.c new file mode 100644 index 000000000..20d962705 --- /dev/null +++ b/libisc/log.c @@ -0,0 +1,1762 @@ +/* + * Copyright (C) 2004-2006 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 1999-2003 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: log.c,v 1.84.18.8 2006/03/02 00:37:22 marka Exp $ */ + +/*! \file + * \author Principal Authors: DCL */ + +#include + +#include +#include +#include +#include + +#include /* dev_t FreeBSD 2.1 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LCTX_MAGIC ISC_MAGIC('L', 'c', 't', 'x') +#define VALID_CONTEXT(lctx) ISC_MAGIC_VALID(lctx, LCTX_MAGIC) + +#define LCFG_MAGIC ISC_MAGIC('L', 'c', 'f', 'g') +#define VALID_CONFIG(lcfg) ISC_MAGIC_VALID(lcfg, LCFG_MAGIC) + +/* + * XXXDCL make dynamic? + */ +#define LOG_BUFFER_SIZE (8 * 1024) + +#ifndef PATH_MAX +#define PATH_MAX 1024 /* AIX and others don't define this. */ +#endif + +/*! + * This is the structure that holds each named channel. A simple linked + * list chains all of the channels together, so an individual channel is + * found by doing strcmp()s with the names down the list. Their should + * be no peformance penalty from this as it is expected that the number + * of named channels will be no more than a dozen or so, and name lookups + * from the head of the list are only done when isc_log_usechannel() is + * called, which should also be very infrequent. + */ +typedef struct isc_logchannel isc_logchannel_t; + +struct isc_logchannel { + char * name; + unsigned int type; + int level; + unsigned int flags; + isc_logdestination_t destination; + ISC_LINK(isc_logchannel_t) link; +}; + +/*! + * The logchannellist structure associates categories and modules with + * channels. First the appropriate channellist is found based on the + * category, and then each structure in the linked list is checked for + * a matching module. It is expected that the number of channels + * associated with any given category will be very short, no more than + * three or four in the more unusual cases. + */ +typedef struct isc_logchannellist isc_logchannellist_t; + +struct isc_logchannellist { + const isc_logmodule_t * module; + isc_logchannel_t * channel; + ISC_LINK(isc_logchannellist_t) link; +}; + +/*! + * This structure is used to remember messages for pruning via + * isc_log_[v]write1(). + */ +typedef struct isc_logmessage isc_logmessage_t; + +struct isc_logmessage { + char * text; + isc_time_t time; + ISC_LINK(isc_logmessage_t) link; +}; + +/*! + * The isc_logconfig structure is used to store the configurable information + * about where messages are actually supposed to be sent -- the information + * that could changed based on some configuration file, as opposed to the + * the category/module specification of isc_log_[v]write[1] that is compiled + * into a program, or the debug_level which is dynamic state information. + */ +struct isc_logconfig { + unsigned int magic; + isc_log_t * lctx; + ISC_LIST(isc_logchannel_t) channels; + ISC_LIST(isc_logchannellist_t) *channellists; + unsigned int channellist_count; + unsigned int duplicate_interval; + int highest_level; + char * tag; + isc_boolean_t dynamic; +}; + +/*! + * This isc_log structure provides the context for the isc_log functions. + * The log context locks itself in isc_log_doit, the internal backend to + * isc_log_write. The locking is necessary both to provide exclusive access + * to the the buffer into which the message is formatted and to guard against + * competing threads trying to write to the same syslog resource. (On + * some systems, such as BSD/OS, stdio is thread safe but syslog is not.) + * Unfortunately, the lock cannot guard against a _different_ logging + * context in the same program competing for syslog's attention. Thus + * There Can Be Only One, but this is not enforced. + * XXXDCL enforce it? + * + * Note that the category and module information is not locked. + * This is because in the usual case, only one isc_log_t is ever created + * in a program, and the category/module registration happens only once. + * XXXDCL it might be wise to add more locking overall. + */ +struct isc_log { + /* Not locked. */ + unsigned int magic; + isc_mem_t * mctx; + isc_logcategory_t * categories; + unsigned int category_count; + isc_logmodule_t * modules; + unsigned int module_count; + int debug_level; + isc_mutex_t lock; + /* Locked by isc_log lock. */ + isc_logconfig_t * logconfig; + char buffer[LOG_BUFFER_SIZE]; + ISC_LIST(isc_logmessage_t) messages; +}; + +/*! + * Used when ISC_LOG_PRINTLEVEL is enabled for a channel. + */ +static const char *log_level_strings[] = { + "debug", + "info", + "notice", + "warning", + "error", + "critical" +}; + +/*! + * Used to convert ISC_LOG_* priorities into syslog priorities. + * XXXDCL This will need modification for NT. + */ +static const int syslog_map[] = { + LOG_DEBUG, + LOG_INFO, + LOG_NOTICE, + LOG_WARNING, + LOG_ERR, + LOG_CRIT +}; + +/*! + * When adding new categories, a corresponding ISC_LOGCATEGORY_foo + * definition needs to be added to . + * + * The default category is provided so that the internal default can + * be overridden. Since the default is always looked up as the first + * channellist in the log context, it must come first in isc_categories[]. + */ +LIBISC_EXTERNAL_DATA isc_logcategory_t isc_categories[] = { + { "default", 0 }, /* "default" must come first. */ + { "general", 0 }, + { NULL, 0 } +}; + +/*! + * See above comment for categories on LIBISC_EXTERNAL_DATA, and apply it to modules. + */ +LIBISC_EXTERNAL_DATA isc_logmodule_t isc_modules[] = { + { "socket", 0 }, + { "time", 0 }, + { "interface", 0 }, + { "timer", 0 }, + { NULL, 0 } +}; + +/*! + * This essentially constant structure must be filled in at run time, + * because its channel member is pointed to a channel that is created + * dynamically with isc_log_createchannel. + */ +static isc_logchannellist_t default_channel; + +/*! + * libisc logs to this context. + */ +LIBISC_EXTERNAL_DATA isc_log_t *isc_lctx = NULL; + +/*! + * Forward declarations. + */ +static isc_result_t +assignchannel(isc_logconfig_t *lcfg, unsigned int category_id, + const isc_logmodule_t *module, isc_logchannel_t *channel); + +static isc_result_t +sync_channellist(isc_logconfig_t *lcfg); + +static isc_result_t +greatest_version(isc_logchannel_t *channel, int *greatest); + +static isc_result_t +roll_log(isc_logchannel_t *channel); + +static void +isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, isc_boolean_t write_once, + isc_msgcat_t *msgcat, int msgset, int msg, + const char *format, va_list args) + ISC_FORMAT_PRINTF(9, 0); + +/*@{*/ +/*! + * Convenience macros. + */ + +#define FACILITY(channel) (channel->destination.facility) +#define FILE_NAME(channel) (channel->destination.file.name) +#define FILE_STREAM(channel) (channel->destination.file.stream) +#define FILE_VERSIONS(channel) (channel->destination.file.versions) +#define FILE_MAXSIZE(channel) (channel->destination.file.maximum_size) +#define FILE_MAXREACHED(channel) (channel->destination.file.maximum_reached) + +/*@}*/ +/**** + **** Public interfaces. + ****/ + +/* + * Establish a new logging context, with default channels. + */ +isc_result_t +isc_log_create(isc_mem_t *mctx, isc_log_t **lctxp, isc_logconfig_t **lcfgp) { + isc_log_t *lctx; + isc_logconfig_t *lcfg = NULL; + isc_result_t result; + + REQUIRE(mctx != NULL); + REQUIRE(lctxp != NULL && *lctxp == NULL); + REQUIRE(lcfgp == NULL || *lcfgp == NULL); + + lctx = isc_mem_get(mctx, sizeof(*lctx)); + if (lctx != NULL) { + lctx->mctx = mctx; + lctx->categories = NULL; + lctx->category_count = 0; + lctx->modules = NULL; + lctx->module_count = 0; + lctx->debug_level = 0; + + ISC_LIST_INIT(lctx->messages); + + result = isc_mutex_init(&lctx->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, lctx, sizeof(*lctx)); + return (result); + } + + /* + * Normally setting the magic number is the last step done + * in a creation function, but a valid log context is needed + * by isc_log_registercategories and isc_logconfig_create. + * If either fails, the lctx is destroyed and not returned + * to the caller. + */ + lctx->magic = LCTX_MAGIC; + + isc_log_registercategories(lctx, isc_categories); + isc_log_registermodules(lctx, isc_modules); + result = isc_logconfig_create(lctx, &lcfg); + + } else + result = ISC_R_NOMEMORY; + + if (result == ISC_R_SUCCESS) + result = sync_channellist(lcfg); + + if (result == ISC_R_SUCCESS) { + lctx->logconfig = lcfg; + + *lctxp = lctx; + if (lcfgp != NULL) + *lcfgp = lcfg; + + } else { + if (lcfg != NULL) + isc_logconfig_destroy(&lcfg); + if (lctx != NULL) + isc_log_destroy(&lctx); + } + + return (result); +} + +isc_result_t +isc_logconfig_create(isc_log_t *lctx, isc_logconfig_t **lcfgp) { + isc_logconfig_t *lcfg; + isc_logdestination_t destination; + isc_result_t result = ISC_R_SUCCESS; + int level = ISC_LOG_INFO; + + REQUIRE(lcfgp != NULL && *lcfgp == NULL); + REQUIRE(VALID_CONTEXT(lctx)); + + lcfg = isc_mem_get(lctx->mctx, sizeof(*lcfg)); + + if (lcfg != NULL) { + lcfg->lctx = lctx; + lcfg->channellists = NULL; + lcfg->channellist_count = 0; + lcfg->duplicate_interval = 0; + lcfg->highest_level = level; + lcfg->tag = NULL; + lcfg->dynamic = ISC_FALSE; + + ISC_LIST_INIT(lcfg->channels); + + /* + * Normally the magic number is the last thing set in the + * structure, but isc_log_createchannel() needs a valid + * config. If the channel creation fails, the lcfg is not + * returned to the caller. + */ + lcfg->magic = LCFG_MAGIC; + + } else + result = ISC_R_NOMEMORY; + + /* + * Create the default channels: + * default_syslog, default_stderr, default_debug and null. + */ + if (result == ISC_R_SUCCESS) { + destination.facility = LOG_DAEMON; + result = isc_log_createchannel(lcfg, "default_syslog", + ISC_LOG_TOSYSLOG, level, + &destination, 0); + } + + if (result == ISC_R_SUCCESS) { + destination.file.stream = stderr; + destination.file.name = NULL; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.maximum_size = 0; + result = isc_log_createchannel(lcfg, "default_stderr", + ISC_LOG_TOFILEDESC, + level, + &destination, + ISC_LOG_PRINTTIME); + } + + if (result == ISC_R_SUCCESS) { + /* + * Set the default category's channel to default_stderr, + * which is at the head of the channels list because it was + * just created. + */ + default_channel.channel = ISC_LIST_HEAD(lcfg->channels); + + destination.file.stream = stderr; + destination.file.name = NULL; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.maximum_size = 0; + result = isc_log_createchannel(lcfg, "default_debug", + ISC_LOG_TOFILEDESC, + ISC_LOG_DYNAMIC, + &destination, + ISC_LOG_PRINTTIME); + } + + if (result == ISC_R_SUCCESS) + result = isc_log_createchannel(lcfg, "null", + ISC_LOG_TONULL, + ISC_LOG_DYNAMIC, + NULL, 0); + + if (result == ISC_R_SUCCESS) + *lcfgp = lcfg; + + else + if (lcfg != NULL) + isc_logconfig_destroy(&lcfg); + + return (result); +} + +isc_logconfig_t * +isc_logconfig_get(isc_log_t *lctx) { + REQUIRE(VALID_CONTEXT(lctx)); + + ENSURE(lctx->logconfig != NULL); + + return (lctx->logconfig); +} + +isc_result_t +isc_logconfig_use(isc_log_t *lctx, isc_logconfig_t *lcfg) { + isc_logconfig_t *old_cfg; + isc_result_t result; + + REQUIRE(VALID_CONTEXT(lctx)); + REQUIRE(VALID_CONFIG(lcfg)); + REQUIRE(lcfg->lctx == lctx); + + /* + * Ensure that lcfg->channellist_count == lctx->category_count. + * They won't be equal if isc_log_usechannel has not been called + * since any call to isc_log_registercategories. + */ + result = sync_channellist(lcfg); + if (result != ISC_R_SUCCESS) + return (result); + + LOCK(&lctx->lock); + + old_cfg = lctx->logconfig; + lctx->logconfig = lcfg; + + UNLOCK(&lctx->lock); + + isc_logconfig_destroy(&old_cfg); + + return (ISC_R_SUCCESS); +} + +void +isc_log_destroy(isc_log_t **lctxp) { + isc_log_t *lctx; + isc_logconfig_t *lcfg; + isc_mem_t *mctx; + isc_logmessage_t *message; + + REQUIRE(lctxp != NULL && VALID_CONTEXT(*lctxp)); + + lctx = *lctxp; + mctx = lctx->mctx; + + if (lctx->logconfig != NULL) { + lcfg = lctx->logconfig; + lctx->logconfig = NULL; + isc_logconfig_destroy(&lcfg); + } + + DESTROYLOCK(&lctx->lock); + + while ((message = ISC_LIST_HEAD(lctx->messages)) != NULL) { + ISC_LIST_UNLINK(lctx->messages, message, link); + + isc_mem_put(mctx, message, + sizeof(*message) + strlen(message->text) + 1); + } + + lctx->buffer[0] = '\0'; + lctx->debug_level = 0; + lctx->categories = NULL; + lctx->category_count = 0; + lctx->modules = NULL; + lctx->module_count = 0; + lctx->mctx = NULL; + lctx->magic = 0; + + isc_mem_put(mctx, lctx, sizeof(*lctx)); + + *lctxp = NULL; +} + +void +isc_logconfig_destroy(isc_logconfig_t **lcfgp) { + isc_logconfig_t *lcfg; + isc_mem_t *mctx; + isc_logchannel_t *channel; + isc_logchannellist_t *item; + char *filename; + unsigned int i; + + REQUIRE(lcfgp != NULL && VALID_CONFIG(*lcfgp)); + + lcfg = *lcfgp; + + /* + * This function cannot be called with a logconfig that is in + * use by a log context. + */ + REQUIRE(lcfg->lctx != NULL && lcfg->lctx->logconfig != lcfg); + + mctx = lcfg->lctx->mctx; + + while ((channel = ISC_LIST_HEAD(lcfg->channels)) != NULL) { + ISC_LIST_UNLINK(lcfg->channels, channel, link); + + if (channel->type == ISC_LOG_TOFILE) { + /* + * The filename for the channel may have ultimately + * started its life in user-land as a const string, + * but in isc_log_createchannel it gets copied + * into writable memory and is not longer truly const. + */ + DE_CONST(FILE_NAME(channel), filename); + isc_mem_free(mctx, filename); + + if (FILE_STREAM(channel) != NULL) + (void)fclose(FILE_STREAM(channel)); + } + + isc_mem_free(mctx, channel->name); + isc_mem_put(mctx, channel, sizeof(*channel)); + } + + for (i = 0; i < lcfg->channellist_count; i++) + while ((item = ISC_LIST_HEAD(lcfg->channellists[i])) != NULL) { + ISC_LIST_UNLINK(lcfg->channellists[i], item, link); + isc_mem_put(mctx, item, sizeof(*item)); + } + + if (lcfg->channellist_count > 0) + isc_mem_put(mctx, lcfg->channellists, + lcfg->channellist_count * + sizeof(ISC_LIST(isc_logchannellist_t))); + + lcfg->dynamic = ISC_FALSE; + if (lcfg->tag != NULL) + isc_mem_free(lcfg->lctx->mctx, lcfg->tag); + lcfg->tag = NULL; + lcfg->highest_level = 0; + lcfg->duplicate_interval = 0; + lcfg->magic = 0; + + isc_mem_put(mctx, lcfg, sizeof(*lcfg)); + + *lcfgp = NULL; +} + +void +isc_log_registercategories(isc_log_t *lctx, isc_logcategory_t categories[]) { + isc_logcategory_t *catp; + + REQUIRE(VALID_CONTEXT(lctx)); + REQUIRE(categories != NULL && categories[0].name != NULL); + + /* + * XXXDCL This somewhat sleazy situation of using the last pointer + * in one category array to point to the next array exists because + * this registration function returns void and I didn't want to have + * change everything that used it by making it return an isc_result_t. + * It would need to do that if it had to allocate memory to store + * pointers to each array passed in. + */ + if (lctx->categories == NULL) + lctx->categories = categories; + + else { + /* + * Adjust the last (NULL) pointer of the already registered + * categories to point to the incoming array. + */ + for (catp = lctx->categories; catp->name != NULL; ) + if (catp->id == UINT_MAX) + /* + * The name pointer points to the next array. + * Ick. + */ + DE_CONST(catp->name, catp); + else + catp++; + + catp->name = (void *)categories; + catp->id = UINT_MAX; + } + + /* + * Update the id number of the category with its new global id. + */ + for (catp = categories; catp->name != NULL; catp++) + catp->id = lctx->category_count++; +} + +isc_logcategory_t * +isc_log_categorybyname(isc_log_t *lctx, const char *name) { + isc_logcategory_t *catp; + + REQUIRE(VALID_CONTEXT(lctx)); + REQUIRE(name != NULL); + + for (catp = lctx->categories; catp->name != NULL; ) + if (catp->id == UINT_MAX) + /* + * catp is neither modified nor returned to the + * caller, so removing its const qualifier is ok. + */ + DE_CONST(catp->name, catp); + else { + if (strcmp(catp->name, name) == 0) + return (catp); + catp++; + } + + return (NULL); +} + +void +isc_log_registermodules(isc_log_t *lctx, isc_logmodule_t modules[]) { + isc_logmodule_t *modp; + + REQUIRE(VALID_CONTEXT(lctx)); + REQUIRE(modules != NULL && modules[0].name != NULL); + + /* + * XXXDCL This somewhat sleazy situation of using the last pointer + * in one category array to point to the next array exists because + * this registration function returns void and I didn't want to have + * change everything that used it by making it return an isc_result_t. + * It would need to do that if it had to allocate memory to store + * pointers to each array passed in. + */ + if (lctx->modules == NULL) + lctx->modules = modules; + + else { + /* + * Adjust the last (NULL) pointer of the already registered + * modules to point to the incoming array. + */ + for (modp = lctx->modules; modp->name != NULL; ) + if (modp->id == UINT_MAX) + /* + * The name pointer points to the next array. + * Ick. + */ + DE_CONST(modp->name, modp); + else + modp++; + + modp->name = (void *)modules; + modp->id = UINT_MAX; + } + + /* + * Update the id number of the module with its new global id. + */ + for (modp = modules; modp->name != NULL; modp++) + modp->id = lctx->module_count++; +} + +isc_logmodule_t * +isc_log_modulebyname(isc_log_t *lctx, const char *name) { + isc_logmodule_t *modp; + + REQUIRE(VALID_CONTEXT(lctx)); + REQUIRE(name != NULL); + + for (modp = lctx->modules; modp->name != NULL; ) + if (modp->id == UINT_MAX) + /* + * modp is neither modified nor returned to the + * caller, so removing its const qualifier is ok. + */ + DE_CONST(modp->name, modp); + else { + if (strcmp(modp->name, name) == 0) + return (modp); + modp++; + } + + return (NULL); +} + +isc_result_t +isc_log_createchannel(isc_logconfig_t *lcfg, const char *name, + unsigned int type, int level, + const isc_logdestination_t *destination, + unsigned int flags) +{ + isc_logchannel_t *channel; + isc_mem_t *mctx; + + REQUIRE(VALID_CONFIG(lcfg)); + REQUIRE(name != NULL); + REQUIRE(type == ISC_LOG_TOSYSLOG || type == ISC_LOG_TOFILE || + type == ISC_LOG_TOFILEDESC || type == ISC_LOG_TONULL); + REQUIRE(destination != NULL || type == ISC_LOG_TONULL); + REQUIRE(level >= ISC_LOG_CRITICAL); + REQUIRE((flags & + (unsigned int)~(ISC_LOG_PRINTALL | ISC_LOG_DEBUGONLY)) == 0); + + /* XXXDCL find duplicate names? */ + + mctx = lcfg->lctx->mctx; + + channel = isc_mem_get(mctx, sizeof(*channel)); + if (channel == NULL) + return (ISC_R_NOMEMORY); + + channel->name = isc_mem_strdup(mctx, name); + if (channel->name == NULL) { + isc_mem_put(mctx, channel, sizeof(*channel)); + return (ISC_R_NOMEMORY); + } + + channel->type = type; + channel->level = level; + channel->flags = flags; + ISC_LINK_INIT(channel, link); + + switch (type) { + case ISC_LOG_TOSYSLOG: + FACILITY(channel) = destination->facility; + break; + + case ISC_LOG_TOFILE: + /* + * The file name is copied because greatest_version wants + * to scribble on it, so it needs to be definitely in + * writable memory. + */ + FILE_NAME(channel) = + isc_mem_strdup(mctx, destination->file.name); + FILE_STREAM(channel) = NULL; + FILE_VERSIONS(channel) = destination->file.versions; + FILE_MAXSIZE(channel) = destination->file.maximum_size; + FILE_MAXREACHED(channel) = ISC_FALSE; + break; + + case ISC_LOG_TOFILEDESC: + FILE_NAME(channel) = NULL; + FILE_STREAM(channel) = destination->file.stream; + FILE_MAXSIZE(channel) = 0; + FILE_VERSIONS(channel) = ISC_LOG_ROLLNEVER; + break; + + case ISC_LOG_TONULL: + /* Nothing. */ + break; + + default: + isc_mem_put(mctx, channel->name, strlen(channel->name) + 1); + isc_mem_put(mctx, channel, sizeof(*channel)); + return (ISC_R_UNEXPECTED); + } + + ISC_LIST_PREPEND(lcfg->channels, channel, link); + + /* + * If default_stderr was redefined, make the default category + * point to the new default_stderr. + */ + if (strcmp(name, "default_stderr") == 0) + default_channel.channel = channel; + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_log_usechannel(isc_logconfig_t *lcfg, const char *name, + const isc_logcategory_t *category, + const isc_logmodule_t *module) +{ + isc_log_t *lctx; + isc_logchannel_t *channel; + isc_result_t result = ISC_R_SUCCESS; + unsigned int i; + + REQUIRE(VALID_CONFIG(lcfg)); + REQUIRE(name != NULL); + + lctx = lcfg->lctx; + + REQUIRE(category == NULL || category->id < lctx->category_count); + REQUIRE(module == NULL || module->id < lctx->module_count); + + for (channel = ISC_LIST_HEAD(lcfg->channels); channel != NULL; + channel = ISC_LIST_NEXT(channel, link)) + if (strcmp(name, channel->name) == 0) + break; + + if (channel == NULL) + return (ISC_R_NOTFOUND); + + if (category != NULL) + result = assignchannel(lcfg, category->id, module, channel); + + else + /* + * Assign to all categories. Note that this includes + * the default channel. + */ + for (i = 0; i < lctx->category_count; i++) { + result = assignchannel(lcfg, i, module, channel); + if (result != ISC_R_SUCCESS) + break; + } + + return (result); +} + +void +isc_log_write(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *format, ...) +{ + va_list args; + + /* + * Contract checking is done in isc_log_doit(). + */ + + va_start(args, format); + isc_log_doit(lctx, category, module, level, ISC_FALSE, + NULL, 0, 0, format, args); + va_end(args); +} + +void +isc_log_vwrite(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, + const char *format, va_list args) +{ + /* + * Contract checking is done in isc_log_doit(). + */ + isc_log_doit(lctx, category, module, level, ISC_FALSE, + NULL, 0, 0, format, args); +} + +void +isc_log_write1(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *format, ...) +{ + va_list args; + + /* + * Contract checking is done in isc_log_doit(). + */ + + va_start(args, format); + isc_log_doit(lctx, category, module, level, ISC_TRUE, + NULL, 0, 0, format, args); + va_end(args); +} + +void +isc_log_vwrite1(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, + const char *format, va_list args) +{ + /* + * Contract checking is done in isc_log_doit(). + */ + isc_log_doit(lctx, category, module, level, ISC_TRUE, + NULL, 0, 0, format, args); +} + +void +isc_log_iwrite(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, + isc_msgcat_t *msgcat, int msgset, int msg, + const char *format, ...) +{ + va_list args; + + /* + * Contract checking is done in isc_log_doit(). + */ + + va_start(args, format); + isc_log_doit(lctx, category, module, level, ISC_FALSE, + msgcat, msgset, msg, format, args); + va_end(args); +} + +void +isc_log_ivwrite(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, + isc_msgcat_t *msgcat, int msgset, int msg, + const char *format, va_list args) +{ + /* + * Contract checking is done in isc_log_doit(). + */ + isc_log_doit(lctx, category, module, level, ISC_FALSE, + msgcat, msgset, msg, format, args); +} + +void +isc_log_iwrite1(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, + isc_msgcat_t *msgcat, int msgset, int msg, + const char *format, ...) +{ + va_list args; + + /* + * Contract checking is done in isc_log_doit(). + */ + + va_start(args, format); + isc_log_doit(lctx, category, module, level, ISC_TRUE, + msgcat, msgset, msg, format, args); + va_end(args); +} + +void +isc_log_ivwrite1(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, + isc_msgcat_t *msgcat, int msgset, int msg, + const char *format, va_list args) +{ + /* + * Contract checking is done in isc_log_doit(). + */ + isc_log_doit(lctx, category, module, level, ISC_TRUE, + msgcat, msgset, msg, format, args); +} + +void +isc_log_setcontext(isc_log_t *lctx) { + isc_lctx = lctx; +} + +void +isc_log_setdebuglevel(isc_log_t *lctx, unsigned int level) { + isc_logchannel_t *channel; + + REQUIRE(VALID_CONTEXT(lctx)); + + LOCK(&lctx->lock); + + lctx->debug_level = level; + /* + * Close ISC_LOG_DEBUGONLY channels if level is zero. + */ + if (lctx->debug_level == 0) + for (channel = ISC_LIST_HEAD(lctx->logconfig->channels); + channel != NULL; + channel = ISC_LIST_NEXT(channel, link)) + if (channel->type == ISC_LOG_TOFILE && + (channel->flags & ISC_LOG_DEBUGONLY) != 0 && + FILE_STREAM(channel) != NULL) { + (void)fclose(FILE_STREAM(channel)); + FILE_STREAM(channel) = NULL; + } + UNLOCK(&lctx->lock); +} + +unsigned int +isc_log_getdebuglevel(isc_log_t *lctx) { + REQUIRE(VALID_CONTEXT(lctx)); + + return (lctx->debug_level); +} + +void +isc_log_setduplicateinterval(isc_logconfig_t *lcfg, unsigned int interval) { + REQUIRE(VALID_CONFIG(lcfg)); + + lcfg->duplicate_interval = interval; +} + +unsigned int +isc_log_getduplicateinterval(isc_logconfig_t *lcfg) { + REQUIRE(VALID_CONTEXT(lcfg)); + + return (lcfg->duplicate_interval); +} + +isc_result_t +isc_log_settag(isc_logconfig_t *lcfg, const char *tag) { + REQUIRE(VALID_CONFIG(lcfg)); + + if (tag != NULL && *tag != '\0') { + if (lcfg->tag != NULL) + isc_mem_free(lcfg->lctx->mctx, lcfg->tag); + lcfg->tag = isc_mem_strdup(lcfg->lctx->mctx, tag); + if (lcfg->tag == NULL) + return (ISC_R_NOMEMORY); + + } else { + if (lcfg->tag != NULL) + isc_mem_free(lcfg->lctx->mctx, lcfg->tag); + lcfg->tag = NULL; + } + + return (ISC_R_SUCCESS); +} + +char * +isc_log_gettag(isc_logconfig_t *lcfg) { + REQUIRE(VALID_CONFIG(lcfg)); + + return (lcfg->tag); +} + +/* XXXDCL NT -- This interface will assuredly be changing. */ +void +isc_log_opensyslog(const char *tag, int options, int facility) { + (void)openlog(tag, options, facility); +} + +void +isc_log_closefilelogs(isc_log_t *lctx) { + isc_logchannel_t *channel; + + REQUIRE(VALID_CONTEXT(lctx)); + + LOCK(&lctx->lock); + for (channel = ISC_LIST_HEAD(lctx->logconfig->channels); + channel != NULL; + channel = ISC_LIST_NEXT(channel, link)) + + if (channel->type == ISC_LOG_TOFILE && + FILE_STREAM(channel) != NULL) { + (void)fclose(FILE_STREAM(channel)); + FILE_STREAM(channel) = NULL; + } + UNLOCK(&lctx->lock); +} + +/**** + **** Internal functions + ****/ + +static isc_result_t +assignchannel(isc_logconfig_t *lcfg, unsigned int category_id, + const isc_logmodule_t *module, isc_logchannel_t *channel) +{ + isc_logchannellist_t *new_item; + isc_log_t *lctx; + isc_result_t result; + + REQUIRE(VALID_CONFIG(lcfg)); + + lctx = lcfg->lctx; + + REQUIRE(category_id < lctx->category_count); + REQUIRE(module == NULL || module->id < lctx->module_count); + REQUIRE(channel != NULL); + + /* + * Ensure lcfg->channellist_count == lctx->category_count. + */ + result = sync_channellist(lcfg); + if (result != ISC_R_SUCCESS) + return (result); + + new_item = isc_mem_get(lctx->mctx, sizeof(*new_item)); + if (new_item == NULL) + return (ISC_R_NOMEMORY); + + new_item->channel = channel; + new_item->module = module; + ISC_LIST_INITANDPREPEND(lcfg->channellists[category_id], + new_item, link); + + /* + * Remember the highest logging level set by any channel in the + * logging config, so isc_log_doit() can quickly return if the + * message is too high to be logged by any channel. + */ + if (channel->type != ISC_LOG_TONULL) { + if (lcfg->highest_level < channel->level) + lcfg->highest_level = channel->level; + if (channel->level == ISC_LOG_DYNAMIC) + lcfg->dynamic = ISC_TRUE; + } + + return (ISC_R_SUCCESS); +} + +/* + * This would ideally be part of isc_log_registercategories(), except then + * that function would have to return isc_result_t instead of void. + */ +static isc_result_t +sync_channellist(isc_logconfig_t *lcfg) { + unsigned int bytes; + isc_log_t *lctx; + void *lists; + + REQUIRE(VALID_CONFIG(lcfg)); + + lctx = lcfg->lctx; + + REQUIRE(lctx->category_count != 0); + + if (lctx->category_count == lcfg->channellist_count) + return (ISC_R_SUCCESS); + + bytes = lctx->category_count * sizeof(ISC_LIST(isc_logchannellist_t)); + + lists = isc_mem_get(lctx->mctx, bytes); + + if (lists == NULL) + return (ISC_R_NOMEMORY); + + memset(lists, 0, bytes); + + if (lcfg->channellist_count != 0) { + bytes = lcfg->channellist_count * + sizeof(ISC_LIST(isc_logchannellist_t)); + memcpy(lists, lcfg->channellists, bytes); + isc_mem_put(lctx->mctx, lcfg->channellists, bytes); + } + + lcfg->channellists = lists; + lcfg->channellist_count = lctx->category_count; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +greatest_version(isc_logchannel_t *channel, int *greatestp) { + /* XXXDCL HIGHLY NT */ + char *basename, *digit_end; + const char *dirname; + int version, greatest = -1; + unsigned int basenamelen; + isc_dir_t dir; + isc_result_t result; + char sep = '/'; +#ifdef _WIN32 + char *basename2; +#endif + + REQUIRE(channel->type == ISC_LOG_TOFILE); + + /* + * It is safe to DE_CONST the file.name because it was copied + * with isc_mem_strdup in isc_log_createchannel. + */ + basename = strrchr(FILE_NAME(channel), sep); +#ifdef _WIN32 + basename2 = strrchr(FILE_NAME(channel), '\\'); + if ((basename != NULL && basename2 != NULL && basename2 > basename) || + (basename == NULL && basename2 != NULL)) { + basename = basename2; + sep = '\\'; + } +#endif + if (basename != NULL) { + *basename++ = '\0'; + dirname = FILE_NAME(channel); + } else { + DE_CONST(FILE_NAME(channel), basename); + dirname = "."; + } + basenamelen = strlen(basename); + + isc_dir_init(&dir); + result = isc_dir_open(&dir, dirname); + + /* + * Replace the file separator if it was taken out. + */ + if (basename != FILE_NAME(channel)) + *(basename - 1) = sep; + + /* + * Return if the directory open failed. + */ + if (result != ISC_R_SUCCESS) + return (result); + + while (isc_dir_read(&dir) == ISC_R_SUCCESS) { + if (dir.entry.length > basenamelen && + strncmp(dir.entry.name, basename, basenamelen) == 0 && + dir.entry.name[basenamelen] == '.') { + + version = strtol(&dir.entry.name[basenamelen + 1], + &digit_end, 10); + if (*digit_end == '\0' && version > greatest) + greatest = version; + } + } + isc_dir_close(&dir); + + *greatestp = ++greatest; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +roll_log(isc_logchannel_t *channel) { + int i, n, greatest; + char current[PATH_MAX + 1]; + char new[PATH_MAX + 1]; + const char *path; + isc_result_t result; + + /* + * Do nothing (not even excess version trimming) if ISC_LOG_ROLLNEVER + * is specified. Apparently complete external control over the log + * files is desired. + */ + if (FILE_VERSIONS(channel) == ISC_LOG_ROLLNEVER) + return (ISC_R_SUCCESS); + + path = FILE_NAME(channel); + + /* + * Set greatest_version to the greatest existing version + * (not the maximum requested version). This is 1 based even + * though the file names are 0 based, so an oldest log of log.1 + * is a greatest_version of 2. + */ + result = greatest_version(channel, &greatest); + if (result != ISC_R_SUCCESS) + return (result); + + /* + * Now greatest should be set to the highest version number desired. + * Since the highest number is one less than FILE_VERSIONS(channel) + * when not doing infinite log rolling, greatest will need to be + * decremented when it is equal to -- or greater than -- + * FILE_VERSIONS(channel). When greatest is less than + * FILE_VERSIONS(channel), it is already suitable for use as + * the maximum version number. + */ + + if (FILE_VERSIONS(channel) == ISC_LOG_ROLLINFINITE || + FILE_VERSIONS(channel) > greatest) + ; /* Do nothing. */ + else + /* + * When greatest is >= FILE_VERSIONS(channel), it needs to + * be reduced until it is FILE_VERSIONS(channel) - 1. + * Remove any excess logs on the way to that value. + */ + while (--greatest >= FILE_VERSIONS(channel)) { + n = snprintf(current, sizeof(current), "%s.%d", + path, greatest); + if (n >= (int)sizeof(current) || n < 0) + result = ISC_R_NOSPACE; + else + result = isc_file_remove(current); + if (result != ISC_R_SUCCESS && + result != ISC_R_FILENOTFOUND) + syslog(LOG_ERR, + "unable to remove log file '%s.%d': %s", + path, greatest, + isc_result_totext(result)); + } + + for (i = greatest; i > 0; i--) { + result = ISC_R_SUCCESS; + n = snprintf(current, sizeof(current), "%s.%d", path, i - 1); + if (n >= (int)sizeof(current) || n < 0) + result = ISC_R_NOSPACE; + if (result == ISC_R_SUCCESS) { + n = snprintf(new, sizeof(new), "%s.%d", path, i); + if (n >= (int)sizeof(new) || n < 0) + result = ISC_R_NOSPACE; + } + if (result == ISC_R_SUCCESS) + result = isc_file_rename(current, new); + if (result != ISC_R_SUCCESS && + result != ISC_R_FILENOTFOUND) + syslog(LOG_ERR, + "unable to rename log file '%s.%d' to " + "'%s.%d': %s", path, i - 1, path, i, + isc_result_totext(result)); + } + + if (FILE_VERSIONS(channel) != 0) { + n = snprintf(new, sizeof(new), "%s.0", path); + if (n >= (int)sizeof(new) || n < 0) + result = ISC_R_NOSPACE; + else + result = isc_file_rename(path, new); + if (result != ISC_R_SUCCESS && + result != ISC_R_FILENOTFOUND) + syslog(LOG_ERR, + "unable to rename log file '%s' to '%s.0': %s", + path, path, isc_result_totext(result)); + } else { + result = isc_file_remove(path); + if (result != ISC_R_SUCCESS && + result != ISC_R_FILENOTFOUND) + syslog(LOG_ERR, "unable to remove log file '%s': %s", + path, isc_result_totext(result)); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +isc_log_open(isc_logchannel_t *channel) { + struct stat statbuf; + isc_boolean_t regular_file; + isc_boolean_t roll = ISC_FALSE; + isc_result_t result = ISC_R_SUCCESS; + const char *path; + + REQUIRE(channel->type == ISC_LOG_TOFILE); + REQUIRE(FILE_STREAM(channel) == NULL); + + path = FILE_NAME(channel); + + REQUIRE(path != NULL && *path != '\0'); + + /* + * Determine type of file; only regular files will be + * version renamed, and only if the base file exists + * and either has no size limit or has reached its size limit. + */ + if (stat(path, &statbuf) == 0) { + regular_file = S_ISREG(statbuf.st_mode) ? ISC_TRUE : ISC_FALSE; + /* XXXDCL if not regular_file complain? */ + if ((FILE_MAXSIZE(channel) == 0 && + FILE_VERSIONS(channel) != ISC_LOG_ROLLNEVER) || + (FILE_MAXSIZE(channel) > 0 && + statbuf.st_size >= FILE_MAXSIZE(channel))) + roll = regular_file; + } else if (errno == ENOENT) + regular_file = ISC_TRUE; + else + result = ISC_R_INVALIDFILE; + + /* + * Version control. + */ + if (result == ISC_R_SUCCESS && roll) { + if (FILE_VERSIONS(channel) == ISC_LOG_ROLLNEVER) + return (ISC_R_MAXSIZE); + result = roll_log(channel); + if (result != ISC_R_SUCCESS) { + if ((channel->flags & ISC_LOG_OPENERR) == 0) { + syslog(LOG_ERR, + "isc_log_open: roll_log '%s' " + "failed: %s", + FILE_NAME(channel), + isc_result_totext(result)); + channel->flags |= ISC_LOG_OPENERR; + } + return (result); + } + } + + result = isc_stdio_open(path, "a", &FILE_STREAM(channel)); + + return (result); +} + +isc_boolean_t +isc_log_wouldlog(isc_log_t *lctx, int level) { + /* + * Try to avoid locking the mutex for messages which can't + * possibly be logged to any channels -- primarily debugging + * messages that the debug level is not high enough to print. + * + * If the level is (mathematically) less than or equal to the + * highest_level, or if there is a dynamic channel and the level is + * less than or equal to the debug level, the main loop must be + * entered to see if the message should really be output. + * + * NOTE: this is UNLOCKED access to the logconfig. However, + * the worst thing that can happen is that a bad decision is made + * about returning without logging, and that's not a big concern, + * because that's a risk anyway if the logconfig is being + * dynamically changed. + */ + + if (lctx == NULL || lctx->logconfig == NULL) + return (ISC_FALSE); + + return (ISC_TF(level <= lctx->logconfig->highest_level || + (lctx->logconfig->dynamic && + level <= lctx->debug_level))); +} + +static void +isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, isc_boolean_t write_once, + isc_msgcat_t *msgcat, int msgset, int msg, + const char *format, va_list args) +{ + int syslog_level; + char time_string[64]; + char level_string[24]; + const char *iformat; + struct stat statbuf; + isc_boolean_t matched = ISC_FALSE; + isc_boolean_t printtime, printtag; + isc_boolean_t printcategory, printmodule, printlevel; + isc_logconfig_t *lcfg; + isc_logchannel_t *channel; + isc_logchannellist_t *category_channels; + isc_result_t result; + + REQUIRE(lctx == NULL || VALID_CONTEXT(lctx)); + REQUIRE(category != NULL); + REQUIRE(module != NULL); + REQUIRE(level != ISC_LOG_DYNAMIC); + REQUIRE(format != NULL); + + /* + * Programs can use libraries that use this logging code without + * wanting to do any logging, thus the log context is allowed to + * be non-existent. + */ + if (lctx == NULL) + return; + + REQUIRE(category->id < lctx->category_count); + REQUIRE(module->id < lctx->module_count); + + if (! isc_log_wouldlog(lctx, level)) + return; + + if (msgcat != NULL) + iformat = isc_msgcat_get(msgcat, msgset, msg, format); + else + iformat = format; + + time_string[0] = '\0'; + level_string[0] = '\0'; + + LOCK(&lctx->lock); + + lctx->buffer[0] = '\0'; + + lcfg = lctx->logconfig; + + category_channels = ISC_LIST_HEAD(lcfg->channellists[category->id]); + + /* + * XXXDCL add duplicate filtering? (To not write multiple times to + * the same source via various channels). + */ + do { + /* + * If the channel list end was reached and a match was made, + * everything is finished. + */ + if (category_channels == NULL && matched) + break; + + if (category_channels == NULL && ! matched && + category_channels != ISC_LIST_HEAD(lcfg->channellists[0])) + /* + * No category/module pair was explicitly configured. + * Try the category named "default". + */ + category_channels = + ISC_LIST_HEAD(lcfg->channellists[0]); + + if (category_channels == NULL && ! matched) + /* + * No matching module was explicitly configured + * for the category named "default". Use the internal + * default channel. + */ + category_channels = &default_channel; + + if (category_channels->module != NULL && + category_channels->module != module) { + category_channels = ISC_LIST_NEXT(category_channels, + link); + continue; + } + + matched = ISC_TRUE; + + channel = category_channels->channel; + category_channels = ISC_LIST_NEXT(category_channels, link); + + if (((channel->flags & ISC_LOG_DEBUGONLY) != 0) && + lctx->debug_level == 0) + continue; + + if (channel->level == ISC_LOG_DYNAMIC) { + if (lctx->debug_level < level) + continue; + } else if (channel->level < level) + continue; + + if ((channel->flags & ISC_LOG_PRINTTIME) != 0 && + time_string[0] == '\0') { + isc_time_t isctime; + + TIME_NOW(&isctime); + isc_time_formattimestamp(&isctime, time_string, + sizeof(time_string)); + } + + if ((channel->flags & ISC_LOG_PRINTLEVEL) != 0 && + level_string[0] == '\0') { + if (level < ISC_LOG_CRITICAL) + snprintf(level_string, sizeof(level_string), + isc_msgcat_get(isc_msgcat, + ISC_MSGSET_LOG, + ISC_MSG_LEVEL, + "level %d: "), + level); + else if (level > ISC_LOG_DYNAMIC) + snprintf(level_string, sizeof(level_string), + "%s %d: ", log_level_strings[0], + level); + else + snprintf(level_string, sizeof(level_string), + "%s: ", log_level_strings[-level]); + } + + /* + * Only format the message once. + */ + if (lctx->buffer[0] == '\0') { + (void)vsnprintf(lctx->buffer, sizeof(lctx->buffer), + iformat, args); + + /* + * Check for duplicates. + */ + if (write_once) { + isc_logmessage_t *message, *new; + isc_time_t oldest; + isc_interval_t interval; + + isc_interval_set(&interval, + lcfg->duplicate_interval, 0); + + /* + * 'oldest' is the age of the oldest messages + * which fall within the duplicate_interval + * range. + */ + TIME_NOW(&oldest); + if (isc_time_subtract(&oldest, &interval, &oldest) + != ISC_R_SUCCESS) + /* + * Can't effectively do the checking + * without having a valid time. + */ + message = NULL; + else + message =ISC_LIST_HEAD(lctx->messages); + + while (message != NULL) { + if (isc_time_compare(&message->time, + &oldest) < 0) { + /* + * This message is older + * than the duplicate_interval, + * so it should be dropped from + * the history. + * + * Setting the interval to be + * to be longer will obviously + * not cause the expired + * message to spring back into + * existence. + */ + new = ISC_LIST_NEXT(message, + link); + + ISC_LIST_UNLINK(lctx->messages, + message, link); + + isc_mem_put(lctx->mctx, + message, + sizeof(*message) + 1 + + strlen(message->text)); + + message = new; + continue; + } + + /* + * This message is in the duplicate + * filtering interval ... + */ + if (strcmp(lctx->buffer, message->text) + == 0) { + /* + * ... and it is a duplicate. + * Unlock the mutex and + * get the hell out of Dodge. + */ + UNLOCK(&lctx->lock); + return; + } + + message = ISC_LIST_NEXT(message, link); + } + + /* + * It wasn't in the duplicate interval, + * so add it to the message list. + */ + new = isc_mem_get(lctx->mctx, + sizeof(isc_logmessage_t) + + strlen(lctx->buffer) + 1); + if (new != NULL) { + /* + * Put the text immediately after + * the struct. The strcpy is safe. + */ + new->text = (char *)(new + 1); + strcpy(new->text, lctx->buffer); + + TIME_NOW(&new->time); + + ISC_LIST_APPEND(lctx->messages, + new, link); + } + } + } + + printtime = ISC_TF((channel->flags & ISC_LOG_PRINTTIME) + != 0); + printtag = ISC_TF((channel->flags & ISC_LOG_PRINTTAG) + != 0 && lcfg->tag != NULL); + printcategory = ISC_TF((channel->flags & ISC_LOG_PRINTCATEGORY) + != 0); + printmodule = ISC_TF((channel->flags & ISC_LOG_PRINTMODULE) + != 0); + printlevel = ISC_TF((channel->flags & ISC_LOG_PRINTLEVEL) + != 0); + + switch (channel->type) { + case ISC_LOG_TOFILE: + if (FILE_MAXREACHED(channel)) { + /* + * If the file can be rolled, OR + * If the file no longer exists, OR + * If the file is less than the maximum size, + * (such as if it had been renamed and + * a new one touched, or it was truncated + * in place) + * ... then close it to trigger reopening. + */ + if (FILE_VERSIONS(channel) != + ISC_LOG_ROLLNEVER || + (stat(FILE_NAME(channel), &statbuf) != 0 && + errno == ENOENT) || + statbuf.st_size < FILE_MAXSIZE(channel)) { + (void)fclose(FILE_STREAM(channel)); + FILE_STREAM(channel) = NULL; + FILE_MAXREACHED(channel) = ISC_FALSE; + } else + /* + * Eh, skip it. + */ + break; + } + + if (FILE_STREAM(channel) == NULL) { + result = isc_log_open(channel); + if (result != ISC_R_SUCCESS && + result != ISC_R_MAXSIZE && + (channel->flags & ISC_LOG_OPENERR) == 0) { + syslog(LOG_ERR, + "isc_log_open '%s' failed: %s", + FILE_NAME(channel), + isc_result_totext(result)); + channel->flags |= ISC_LOG_OPENERR; + } + if (result != ISC_R_SUCCESS) + break; + channel->flags &= ~ISC_LOG_OPENERR; + } + /* FALLTHROUGH */ + + case ISC_LOG_TOFILEDESC: + fprintf(FILE_STREAM(channel), "%s%s%s%s%s%s%s%s%s%s\n", + printtime ? time_string : "", + printtime ? " " : "", + printtag ? lcfg->tag : "", + printtag ? ": " : "", + printcategory ? category->name : "", + printcategory ? ": " : "", + printmodule ? (module != NULL ? module->name + : "no_module") + : "", + printmodule ? ": " : "", + printlevel ? level_string : "", + lctx->buffer); + + fflush(FILE_STREAM(channel)); + + /* + * If the file now exceeds its maximum size + * threshold, note it so that it will not be logged + * to any more. + */ + if (FILE_MAXSIZE(channel) > 0) { + INSIST(channel->type == ISC_LOG_TOFILE); + + /* XXXDCL NT fstat/fileno */ + /* XXXDCL complain if fstat fails? */ + if (fstat(fileno(FILE_STREAM(channel)), + &statbuf) >= 0 && + statbuf.st_size > FILE_MAXSIZE(channel)) + FILE_MAXREACHED(channel) = ISC_TRUE; + } + + break; + + case ISC_LOG_TOSYSLOG: + if (level > 0) + syslog_level = LOG_DEBUG; + else if (level < ISC_LOG_CRITICAL) + syslog_level = LOG_CRIT; + else + syslog_level = syslog_map[-level]; + + (void)syslog(FACILITY(channel) | syslog_level, + "%s%s%s%s%s%s%s%s%s%s", + printtime ? time_string : "", + printtime ? " " : "", + printtag ? lcfg->tag : "", + printtag ? ": " : "", + printcategory ? category->name : "", + printcategory ? ": " : "", + printmodule ? (module != NULL ? module->name + : "no_module") + : "", + printmodule ? ": " : "", + printlevel ? level_string : "", + lctx->buffer); + break; + + case ISC_LOG_TONULL: + break; + + } + + } while (1); + + UNLOCK(&lctx->lock); +}