aboutsummaryrefslogtreecommitdiff
path: root/ports/winnt/ntpd/nt_ppsimpl.c
diff options
context:
space:
mode:
Diffstat (limited to 'ports/winnt/ntpd/nt_ppsimpl.c')
-rw-r--r--ports/winnt/ntpd/nt_ppsimpl.c770
1 files changed, 770 insertions, 0 deletions
diff --git a/ports/winnt/ntpd/nt_ppsimpl.c b/ports/winnt/ntpd/nt_ppsimpl.c
new file mode 100644
index 000000000000..2a7c0d88f1d9
--- /dev/null
+++ b/ports/winnt/ntpd/nt_ppsimpl.c
@@ -0,0 +1,770 @@
+/*
+ * nt_ppsimpl.c - PPS API client implementation
+ *
+ * Written by Juergen Perlinger (perlinger@ntp.org) for the NTP project.
+ * The contents of 'html/copyright.html' apply.
+ * ----------------------------------------------------------------------
+ * Most of this code is from the the original 'timepps.h' for windows
+ * where these functions where coded as 'inline'. While this was perhaps
+ * a convenient thing to do, using this amount of code as 'static inline'
+ * functions is generally a Bad Idea (tm).
+ *
+ * Not to mention that there are some static variables that got duplicated
+ * into the various modules...
+ */
+
+#include <config.h>
+
+#include <stddef.h> /* offsetof() */
+#include <io.h> /* _get_osfhandle() */
+
+#include "timepps.h"
+#include "ntp_stdlib.h"
+#include "lib_strbuf.h"
+#include "ntp_iocpltypes.h"
+#include "ntp_iocplmem.h"
+
+struct InstListNode {
+ struct InstListNode * next;
+ pps_handle_t ppsu;
+ DevCtx_t * devu;
+};
+typedef struct ProvListNode ProvListNode_t;
+
+static struct InstListNode * g_active_units;
+static ppsapi_provider * g_provider_list;
+static ppsapi_provider * g_curr_provider;
+
+static void
+ppsu_register(
+ pps_handle_t ppsu,
+ DevCtx_t * devu)
+{
+ struct InstListNode * node;
+
+ if (devu && (node = IOCPLPoolAlloc(sizeof(*node), "PPS registration"))) {
+ node->next = g_active_units;
+ node->ppsu = ppsu;
+ node->devu = DevCtxAttach(devu);
+ devu->pps_active = TRUE;
+ g_active_units = node;
+ }
+}
+
+static void
+ppsu_remove(
+ pps_handle_t ppsu)
+{
+ struct InstListNode ** link;
+ struct InstListNode * node;
+
+ link = &g_active_units;
+ while (NULL != (node = *link)) {
+ if (node->ppsu == ppsu) {
+ node->devu->pps_active = FALSE;
+ DevCtxDetach(node->devu);
+ *link = node->next;
+ IOCPLPoolFree(node, "PPS registration");
+ } else {
+ link = &node->next;
+ }
+ }
+}
+
+static HKEY
+myRegOpenKey(
+ const char * szSubKey)
+{
+ static const char * const s_RegKey =
+ "SYSTEM\\CurrentControlSet\\services\\NTP";
+
+ HKEY hkey1 = NULL;
+ HKEY hkey2 = NULL;
+ DWORD rc;
+
+ rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE, s_RegKey, 0, KEY_READ, &hkey1);
+ if (ERROR_SUCCESS != rc)
+ return NULL;
+ if (!(szSubKey && *szSubKey))
+ return hkey1;
+
+ rc = RegOpenKeyExA(hkey1, szSubKey, 0, KEY_READ, &hkey2);
+ RegCloseKey(hkey1);
+ if (ERROR_SUCCESS != rc)
+ return NULL;
+ return hkey2;
+}
+
+static char*
+myRegReadMultiString(
+ HKEY hKey ,
+ const char *szValue,
+ DWORD *pSize )
+{
+ char * endp;
+ char * retv = NULL;
+ DWORD rSize = 0, rType = REG_NONE, rc;
+
+ /* take two turns: one to get the size, another one to malloc & read */
+ do {
+ if (rType != REG_NONE) {
+ retv = malloc(rSize += 2);
+ if (NULL == retv)
+ goto fail;
+ }
+ rc = RegQueryValueExA(hKey, szValue, NULL, &rType, retv, &rSize);
+ if (ERROR_SUCCESS != rc || (REG_SZ != rType && REG_MULTI_SZ != rType))
+ goto fail;
+ } while (NULL == retv);
+
+ /* trim trailing NULs and ensure two of them */
+ endp = retv + rSize;
+ while (endp != retv && endp[-1])
+ --endp;
+ if (endp != retv) {
+ endp[0] = endp[1] = '\0';
+ if (NULL != pSize)
+ *pSize = (DWORD)(endp - retv);
+ return retv;
+ }
+fail:
+ free(retv);
+ if (NULL != pSize)
+ *pSize = 0;
+ return NULL;
+}
+
+static DWORD
+myRegReadDWord(
+ HKEY hKey ,
+ const char *szValue,
+ DWORD Default)
+{
+ DWORD rc, rSize, rType, rValue;
+
+ rSize = sizeof(rValue);
+ rc = RegQueryValueExA(hKey, szValue, NULL, &rType, (PBYTE)&rValue, &rSize);
+ if (rc != ERROR_SUCCESS || rSize != sizeof(rValue) || rType != REG_DWORD)
+ rValue = Default;
+ return rValue;
+}
+
+
+static pps_handle_t
+internal_create_pps_handle(
+ void * prov_context
+ )
+{
+ pps_unit_t * punit = NULL;
+
+ if (NULL == g_curr_provider)
+ fprintf(stderr, "create_pps_handle: provider backend called me outside time_pps_create\n");
+ else
+ punit = calloc(1, sizeof(pps_unit_t));
+
+ if (NULL != punit) {
+ punit->provider = g_curr_provider;
+ punit->context = prov_context;
+ punit->magic = PPSAPI_MAGIC_UNIT;
+ }
+ return (pps_handle_t)punit;
+}
+
+static pps_unit_t *
+unit_from_ppsapi_handle(
+ pps_handle_t handle
+ )
+{
+ pps_unit_t *punit = (pps_unit_t *)handle;
+ if (!(punit && PPSAPI_MAGIC_UNIT == punit->magic))
+ punit = NULL;
+ return punit;
+}
+
+/* ntpd on Windows only looks to errno after finding 'GetLastError()'
+ * returns NO_ERROR. To accomodate its use of msyslog in portable code
+ * such as refclock_atom.c, this implementation always clears the
+ * Windows error code using 'SetLastError(NO_ERROR)' when returning an
+ * errno. This is also a good idea for any non-ntpd clients as they
+ * should rely only the errno for PPSAPI functions.
+ */
+static int
+set_pps_errno(
+ int e
+)
+{
+ SetLastError(NO_ERROR);
+ errno = e;
+ return -1;
+}
+
+
+/*Format a Windows errro into a temporary buffer from buffer lib */
+static char *
+fmt_err(
+ DWORD ec
+ )
+{
+ char * buff = NULL;
+ char * endp = NULL;
+
+ /* get buffer & format message, ensure termination */
+ LIB_GETBUF(buff);
+ FormatMessageA(
+ FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ ec,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ buff, LIB_BUFLENGTH,
+ NULL);
+ buff[LIB_BUFLENGTH - 1] = '\0';
+ /* strip trailing whitespace & CR/LF stuff & the trailing dot */
+ endp = buff + strlen(buff);
+ while (endp != buff && endp[-1] <= ' ')
+ --endp;
+ if (endp != buff && endp[-1] == '.')
+ --endp;
+ *endp = '\0';
+ /* If windows fails to format this, resort to numeric output... */
+ if (!*buff) {
+ snprintf(buff, LIB_BUFLENGTH,
+ "(unknown windows error %lu / 0x%08lx)",
+ (unsigned long)ec, (unsigned long)ec);
+ }
+ /* That's it for now... */
+ return buff;
+}
+
+/* Cleanup & error return for actual loading steps */
+static int
+cleanup_load(
+ ppsapi_provider * prov,
+ HMODULE hmod,
+ int errc
+)
+{
+ /* if possible, decref the library handle */
+ if (NULL != hmod)
+ FreeLibrary(hmod);
+ /* if possible, drop our provider structure */
+ if (prov) {
+ free(prov->short_name);
+ free(prov->full_name);
+ free(prov);
+ }
+ /* error code pass-through */
+ return errc;
+}
+
+/* Get the directory path (!!) of the calling process. We want the true
+ * 8bit character version, and we should not trust '_pgmptr' or
+ * '_get_pgmptr()' too much. There's still some doubt around
+ * 'GetModuleFileNameA()', but all in all this seems to be the most
+ * reliable solution, even if it's a PITA with the buffer sizes.
+ */
+static char*
+get_module_path(void)
+{
+ static const size_t s_smax = 4096;
+
+ char * buff = NULL;
+ char * lsep;
+ DWORD slen, nlen;
+
+ for (slen = 128; slen <= s_smax; slen <<= 1) {
+ buff = realloc(buff, slen);
+ if (NULL == buff)
+ goto fail;
+ nlen = GetModuleFileNameA(NULL, buff, slen);
+ if (0 == nlen)
+ goto fail;
+ if (nlen < slen)
+ break;
+ }
+ if (slen > s_smax)
+ goto fail;
+ lsep = strrchr(buff, '\\');
+ if (NULL == lsep)
+ goto fail;
+ *++lsep = '\0';
+ return realloc(buff, (size_t)(lsep - buff) + 1);
+
+fail:
+ free(buff);
+ return NULL;
+}
+
+static char *
+get_provider_list(void)
+{
+ static char * s_Value = NULL;
+
+ HKEY hKey;
+ DWORD rSize;
+ char *cp, *op;
+
+ if (s_Value != NULL)
+ return (*s_Value) ? s_Value : NULL;
+
+ /*
+ ** try registry first
+ */
+ hKey = myRegOpenKey(NULL);
+ if (NULL == hKey)
+ goto regfail;
+
+ s_Value = myRegReadMultiString(hKey, "PPSProviders", &rSize);
+ if (NULL == s_Value)
+ goto regfail;
+
+ /* make sure we have backslashes in the path */
+ for (cp = s_Value; rSize; --rSize, ++cp)
+ if (*cp == '/')
+ *cp = '\\';
+regfail:
+ if (NULL != hKey)
+ RegCloseKey(hKey);
+
+ if (s_Value && *s_Value)
+ return s_Value;
+
+ /*
+ ** try environment next.
+ */
+ free(s_Value);
+ s_Value = NULL;
+
+ /* try to get env var */
+ cp = getenv("PPSAPI_DLLS");
+ if (!(cp && *cp))
+ goto envfail;
+
+ /* get size & allocate buffer */
+ rSize = strlen(cp);
+ s_Value = malloc(rSize + 2);
+ if (s_Value == NULL)
+ goto envfail;
+
+ /* copy string value and convert to MULTI_SZ.
+ * Converts sequences of ';' to a single NUL byte, and rplaces
+ * slashes by backslashes on the fly.
+ */
+ for (op = s_Value; *cp; ++cp) {
+ if (*cp == '/') {
+ *op++ = '\\';
+ } else if (*cp == ';') {
+ if (op != s_Value && op[-1])
+ *op++ = '\0';
+ } else {
+ *op++ = *cp;
+ }
+ }
+ cp[0] = '\0';
+ cp[1] = '\0';
+ return s_Value;
+
+envfail:
+ free(s_Value);
+ s_Value = calloc(2, 1);
+ return s_Value;
+}
+
+
+/* Iteration helper for the provider list. Naked names (without *any*
+ * path) will be prepended with path to the executable running this
+ * code. While this was not necessary until Win7, newer versions of
+ * Windows seem to have tighter restrictions from where to load code, at
+ * least as long as the binary is not signed.
+ */
+static char*
+provlist_next_item(
+ const char ** iter
+ )
+{
+ static char * s_modpath /* = NULL */;
+ static char s_nullstr[1] /* = { '\0' } */;
+
+ const char *phead, *phold;
+ char *retv, *endp;
+ int/*BOOL*/ nodir;
+ DWORD slen, mlen;
+
+ /* get next item -- might be start of a new round or the end */
+again:
+ if (*iter == NULL)
+ *iter = phead = get_provider_list();
+ else
+ *iter = phead = *iter + strlen(*iter) + 1;
+ if (!(phead && *phead)) {
+ *iter = NULL;
+ return NULL;
+ }
+
+ /* Inspect the next section of input string. It must be
+ * either an absolute path or just a name.
+ */
+ if (isalpha((u_char)phead[0]) && phead[1] == ':' && phead[2] == '\\') {
+ nodir = FALSE;
+ } else {
+ nodir = TRUE;
+ phold = phead;
+ while (NULL != (endp = strpbrk(phold, "\\:")))
+ phold = endp + 1;
+ if (phead != phold) {
+ msyslog(LOG_WARNING,
+ "pps api: path component(s) of '%s' ignored, use '%s'",
+ phead, phold);
+ phead = phold;
+ }
+ }
+ if (!*phead || strchr("\\.:", (u_char)phead[strlen(phead) - 1]))
+ goto again; /* empty or looks like a directory! */
+
+ /* Make sure we have a proper module path when we need one. */
+ if (nodir && NULL == s_modpath) {
+ s_modpath = get_module_path();
+ if (NULL == s_modpath)
+ s_modpath = s_nullstr;
+ }
+
+ /* Prepare buffer for copy of file name. */
+ slen = (DWORD)strlen(phead); /* 4GB string should be enough... */
+ if (nodir && NULL != s_modpath) {
+ /* Prepend full path to executable to the name. */
+ mlen = (DWORD)strlen(s_modpath);
+ endp = retv = malloc(mlen + slen + 1);
+ if (NULL != endp) {
+ memcpy(endp, s_modpath, mlen);
+ endp += mlen;
+ }
+ } else {
+ endp = retv = malloc(slen + 1u);
+ }
+ /* Copy with conversion from '/' to '\\' */
+ if (NULL != endp) {
+ memcpy(endp, phead, slen);
+ endp[slen] = '\0';
+ }
+ return retv;
+}
+
+
+/* Try to load & init a provider DLL. (NOT a device instance!) */
+static int
+load_pps_provider(
+ const char * dllpath
+ )
+{
+ static const char msgfmt[] = "load_pps_provider: '%s': %s";
+
+ char short_name[16];
+ char full_name[64];
+ ppsapi_provider * prov = NULL;
+ HMODULE hmod = NULL;
+ pppsapi_prov_init pprov_init;
+ int errc;
+
+ prov = calloc(1, sizeof(*prov));
+ if (NULL == prov) {
+ errc = errno;
+ msyslog(LOG_WARNING, msgfmt, dllpath,
+ strerror(errc));
+ return errc;
+ }
+
+ hmod = LoadLibraryA(dllpath);
+ if (NULL == hmod) {
+ msyslog(LOG_WARNING, msgfmt, dllpath,
+ fmt_err(GetLastError()));
+ return cleanup_load(prov, hmod, ENOENT);
+ }
+
+ pprov_init = (pppsapi_prov_init)GetProcAddress(hmod, "ppsapi_prov_init");
+ if (NULL == pprov_init) {
+ msyslog(LOG_WARNING, msgfmt, dllpath,
+ "main entry point not found");
+ return cleanup_load(prov, hmod, EFAULT);
+ }
+
+ prov->caps = (*pprov_init)(PPSAPI_TIMEPPS_PROV_VER,
+ &internal_create_pps_handle,
+ &pps_ntp_timestamp_from_counter,
+ short_name, sizeof(short_name),
+ full_name, sizeof(full_name));
+ if (!prov->caps) {
+ msyslog(LOG_WARNING, msgfmt, dllpath,
+ "no capabilities");
+ return cleanup_load(prov, hmod, EACCES);
+ }
+
+ prov->short_name = (*short_name) ? _strdup(short_name) : NULL;
+ prov->full_name = (*full_name ) ? _strdup(full_name ) : NULL;
+ if (NULL == prov->short_name || NULL == prov->full_name) {
+ msyslog(LOG_WARNING, msgfmt, dllpath,
+ "missing names");
+ return cleanup_load(prov, hmod, EINVAL);
+ }
+
+ prov->ptime_pps_create = (provtime_pps_create)
+ GetProcAddress(hmod, "prov_time_pps_create");
+ prov->ptime_pps_destroy = (provtime_pps_destroy)
+ GetProcAddress(hmod, "prov_time_pps_destroy");
+ prov->ptime_pps_setparams = (provtime_pps_setparams)
+ GetProcAddress(hmod, "prov_time_pps_setparams");
+ prov->ptime_pps_fetch = (provtime_pps_fetch)
+ GetProcAddress(hmod, "prov_time_pps_fetch");
+ prov->ptime_pps_kcbind = (provtime_pps_kcbind)
+ GetProcAddress(hmod, "prov_time_pps_kcbind");
+
+ if (NULL == prov->ptime_pps_create ||
+ NULL == prov->ptime_pps_destroy ||
+ NULL == prov->ptime_pps_setparams ||
+ NULL == prov->ptime_pps_fetch ||
+ NULL == prov->ptime_pps_kcbind )
+ {
+ msyslog(LOG_WARNING, msgfmt, prov->short_name,
+ "missing entry point");
+ return cleanup_load(prov, hmod, EINVAL);
+ }
+
+ prov->next = g_provider_list;
+ g_provider_list = prov;
+
+ return 0;
+}
+
+
+static ppsapi_provider*
+get_first_provider(void)
+{
+ const char * itpos;
+ char * dll;
+ ppsapi_provider *prov, *hold;
+ int err;
+
+ /* check if we have done our work so far... */
+ if (g_provider_list == INVALID_HANDLE_VALUE)
+ return NULL;
+ if (g_provider_list != NULL)
+ return g_provider_list;
+
+ itpos = NULL;
+ while (NULL != (dll = provlist_next_item(&itpos))) {
+ err = load_pps_provider(dll);
+ if (err)
+ msyslog(LOG_ERR, "time_pps_create: load failed (%s) --> %d / %s",
+ dll, err, strerror(err));
+ else
+ msyslog(LOG_INFO, "time_pps_create: loaded '%s'",
+ dll);
+ free(dll);
+ }
+
+ /* reverse the list, possibly mark as EMPTY */
+ prov = g_provider_list;
+ if (NULL != prov) {
+ g_provider_list = NULL;
+ do {
+ hold = prov;
+ prov = hold->next;
+ hold->next = g_provider_list;
+ g_provider_list = hold;
+ } while (prov);
+ prov = g_provider_list;
+ } else {
+ g_provider_list = INVALID_HANDLE_VALUE;
+ }
+ return prov;
+}
+
+int
+time_pps_create(
+ int filedes,/* device file descriptor */
+ pps_handle_t * phandle /* returned handle */
+ )
+{
+ HANDLE winhandle;
+ ppsapi_provider * prov;
+ pps_handle_t ppshandle;
+ int err;
+
+ if (NULL == phandle)
+ return set_pps_errno(EFAULT);
+
+ winhandle = (HANDLE)_get_osfhandle(filedes);
+ if (INVALID_HANDLE_VALUE == winhandle)
+ return set_pps_errno(EBADF);
+
+ /* Hand off to each provider in turn until one returns a PPS
+ * handle or they've all declined.
+ *
+ * [Bug 3139] Since we potentially tried a series of DLLs, it's
+ * a good question what the returned error should be if all of
+ * them failed; Returning the error from the last attempt is as
+ * good as any but for single DLL (which is the normal case)
+ * this provides slightly more information.
+ */
+ err = ENOEXEC;
+ prov = get_first_provider();
+ if (NULL == prov) {
+ msyslog(LOG_ERR, "time_pps_create: %s",
+ "no providers available");
+ return set_pps_errno(err);
+ } else do {
+ ppshandle = 0;
+ g_curr_provider = prov;
+ err = (*prov->ptime_pps_create)(winhandle, &ppshandle);
+ g_curr_provider = NULL;
+ if (!err && ppshandle) {
+ *phandle = ppshandle;
+ ppsu_register(ppshandle, serial_devctx(winhandle));
+ return 0;
+ }
+ msyslog(LOG_INFO, "time_pps_create: provider '%s' failed: %d / %s",
+ prov->short_name, err, strerror(err));
+ } while (NULL != (prov = prov->next));
+
+ msyslog(LOG_ERR, "time_pps_create: %s",
+ "all providers failed");
+ return set_pps_errno(err);
+}
+
+
+int
+time_pps_destroy(
+ pps_handle_t handle
+ )
+{
+ pps_unit_t * punit = unit_from_ppsapi_handle(handle);
+ int err = 0;
+
+ /* Check for valid arguments */
+ if (NULL == punit)
+ return set_pps_errno(EBADF);
+ /* Call provider. Note the handle is gone anyway... */
+ ppsu_remove(handle);
+ err = (*punit->provider->ptime_pps_destroy)(punit, punit->context);
+ free(punit);
+ if (err)
+ return set_pps_errno(err);
+ return 0;
+}
+
+
+int
+time_pps_setparams(
+ pps_handle_t handle,
+ const pps_params_t *params
+ )
+{
+ pps_unit_t * punit = unit_from_ppsapi_handle(handle);
+ int err = 0;
+
+ /* Check for valid arguments */
+ if (NULL == punit)
+ return set_pps_errno(EBADF);
+ if (NULL == params)
+ return set_pps_errno(EFAULT);
+ /* Call provider */
+ err = (*punit->provider->ptime_pps_setparams)(punit, punit->context, params);
+ if (err)
+ return set_pps_errno(err);
+ return 0;
+}
+
+
+int
+time_pps_getparams(
+ pps_handle_t handle,
+ pps_params_t * params_buf
+ )
+{
+ pps_unit_t * punit = unit_from_ppsapi_handle(handle);
+
+ /* Check for valid arguments */
+ punit;
+ if (NULL == punit)
+ return set_pps_errno(EBADF);
+ if (NULL == params_buf)
+ return set_pps_errno(EFAULT);
+ /* Copy out parameters */
+ *params_buf = punit->params;
+ return 0;
+}
+
+
+int
+time_pps_getcap(
+ pps_handle_t handle,
+ int * pmode
+ )
+{
+ pps_unit_t * punit = unit_from_ppsapi_handle(handle);
+
+ /* Check for valid arguments */
+ if (NULL == punit)
+ return set_pps_errno(EBADF);
+ if (NULL == pmode)
+ return set_pps_errno(EFAULT);
+ /* Copy out capabilities */
+ *pmode = punit->provider->caps;
+ return 0;
+}
+
+
+int
+time_pps_fetch(
+ pps_handle_t handle,
+ const int tsformat,
+ pps_info_t * pinfo,
+ const struct timespec * ptimeout
+ )
+{
+ pps_unit_t * punit = unit_from_ppsapi_handle(handle);
+ int err = 0;
+
+ /* Check for valid arguments */
+ if (NULL == punit)
+ return set_pps_errno(EBADF);
+ if (NULL == pinfo)
+ return set_pps_errno(EFAULT);
+ /* Fetch timestamps */
+ err = (*punit->provider->ptime_pps_fetch)(punit,
+ punit->context,
+ tsformat,
+ pinfo,
+ ptimeout);
+
+ if (err)
+ return set_pps_errno(err);
+ return 0;
+}
+
+
+int
+time_pps_kcbind(
+ pps_handle_t handle,
+ const int kernel_consumer,
+ const int edge, const int tsformat
+ )
+{
+ pps_unit_t * punit = unit_from_ppsapi_handle(handle);
+ int err = 0;
+
+ /* Check for valid arguments */
+ if (NULL == punit)
+ return set_pps_errno(EBADF);
+ /* Call provider */
+ err = (*punit->provider->ptime_pps_kcbind)(
+ punit,
+ punit->context,
+ kernel_consumer,
+ edge,
+ tsformat);
+
+ if (err)
+ return set_pps_errno(err);
+ return 0;
+}
+
+/* -*- that's all folks! -*- */