/* * 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 #include /* offsetof() */ #include /* _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; } } *op++ = '\0'; *op = '\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! -*- */