Пример #1
0
/** ========================================================================
 * =========================================================================
 */
PslError
psl_inet_make_fd_non_blocking(int const fd, const char* const userLabel,
                              const void* const cookie)
{
    /// Make it non-blocking
    int const flags = fcntl(fd, F_GETFL, 0);
    if (-1 == flags) {
        int const savederrno = errno;
        PSL_LOG_ERROR("%s (%s=%p): ERROR: fcntl(%d, F_GETFL, 0) failed; " \
                      "errno=%d (%s)", __func__, userLabel, cookie, fd,
                      savederrno, strerror(savederrno));
        return psl_err_pslerror_from_errno(savederrno, PSL_ERR_SOCKET_CONFIG);
    }

    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
        int const savederrno = errno;
        PSL_LOG_ERROR("%s (%s=%p): ERROR: fcntl(%d, F_SETFL, " \
                      "flags | O_NONBLOCK) failed; errno=%d (%s)",
                      __func__, userLabel, cookie, fd, savederrno,
                      strerror(savederrno));
        return psl_err_pslerror_from_errno(savederrno, PSL_ERR_SOCKET_CONFIG);
    }

    return 0;
}//psl_inet_make_fd_non_blocking
/** ========================================================================
 * =========================================================================
 */
static void
chan_fsm_free(PslChanFsm* const pFsm)
{
    PSL_LOG_INFO("%s (fsm=%p)", __func__, pFsm);

    PSL_ASSERT(pFsm);

    /**
     * If the FSM was successfully started, then we should have been
     * finalized by the channel via psl_chan_fsm_finalize() before 
     * our reference count dropped to zero 
     */
    PSL_ASSERT(!pFsm->fsmStarted || pFsm->fsmFinalized);

    /// Our sock should be closed by the state handlers
    if (pFsm->fd >= 0) {
        PSL_LOG_ERROR("%s: ERROR: pFsm->fd not closed!", __func__);
    }

    if (!pFsm->fsmStarted) {
        chan_fsm_destroy_widgets(pFsm);
    }

    g_free(pFsm);

    PSL_LOG_DEBUGLOW("%s (fsm=%p): LEAVING", __func__, pFsm);
}
Пример #3
0
/** ========================================================================
 * =========================================================================
 */
PslError
psl_multi_fd_watch_remove_fd(PslMultiFdWatchSource* source, int const fd)
{
    gboolean removed;
    PslMultiFdDescriptor* desc;

    PSL_LOG_DEBUGLOW("%s (watch=%p), fd=%d", __func__, source, (int)fd);

    desc = (PslMultiFdDescriptor*) g_hash_table_lookup(source->descTable,
                                                       GINT_TO_POINTER(fd));

    if (!desc) {
        PSL_LOG_ERROR("%s (watch=%p): ERROR: fd=%d not found", __func__,
                      source, (int)fd);
        return PSL_ERR_INVAL;
    }

    if (desc->pollAdded) {
        g_source_remove_poll((GSource*)source, &desc->pollFd);
    }

    removed = g_hash_table_remove(source->descTable, GINT_TO_POINTER(fd));
    PSL_ASSERT(removed);

    return 0;
}
Пример #4
0
/** ========================================================================
 * =========================================================================
 */
PslError
psl_multi_fd_watch_set_poll_timeout(PslMultiFdWatchSource* source,
                                    int const timeoutMillisec)
{
    PSL_LOG_DEBUG("%s (watch=%p), timeout=%d millisec",
                  __func__, source, (int)timeoutMillisec);

    if (timeoutMillisec < -1) {
        PSL_LOG_ERROR("%s (watch=%p): ERROR: passed negative timemeout, " \
                      "but not -1: %d",
                      __func__, source, (int)timeoutMillisec);
        return PSL_ERR_INVAL;
    }

    source->restartTimer = true;

    if (timeoutMillisec == -1) {
        source->timeoutMillisec = MULTI_FD_WATCH_INFINITE_MSEC;
    }

    else { // timeoutMillisec >= 0
        source->timeoutMillisec = timeoutMillisec;
    }

    return 0;
}
/** ========================================================================
 * =========================================================================
 */
static PslSmeEventStatus
chan_fsm_closed_state_handler(PslChanFsmClosedState*    const pState,
                              PslChanFsm*               const pFsm,
                              PslSmeEventId             const evtId,
                              const PslChanFsmEvtArg*   const evtArg)
{   
    switch (evtId) 
    {
    case kFsmEventEnterScope:
        if (pFsm->fsmClosed) {
            PSL_LOG_ERROR("%s (fsm=%p): ERROR: entering CLOSED state *again*",
                          __func__, pFsm);
        }
        if (pFsm->fd >= 0 &&
            !(pFsm->userSettings.fdOpts & kPmSockFileDescOpt_doNotClose)) {
            shutdown(pFsm->fd, SHUT_RDWR);
            if (0 != close(pFsm->fd)) {
                const int   savederrno = errno;
                PSL_LOG_ERROR(
                    "%s (fsm=%p): ERROR closing comms fd: fd=%d, errno=%d (%s)",
                    __func__, pFsm, pFsm->fd, savederrno,
                    strerror(savederrno));
            }
        }
        pFsm->fd = PSL_CHAN_FSM_CLOSED_FD_VALUE;
        pFsm->fsmClosed = true;
        return kPslSmeEventStatus_success;
        break;

    case kFsmEventExitScope:
        return kPslSmeEventStatus_success;
        break;

    case kFsmEventBegin:
        (void)psl_multi_fd_watch_reset(pFsm->fdWatchInfo.fdWatch);
        return kPslSmeEventStatus_success;
        break;

    default:
        break; ///< allow default processing by parent
    }

    return kPslSmeEventStatus_passToParent;
}//chan_fsm_closed_state_handler
Пример #6
0
/** ========================================================================
 * =========================================================================
 */
PslError
psl_inet_connect_sock(int         const s,
                      int         const addrFamily,
                      const char* const addrStr,
                      int         const port,
                      const char* const userLabel,
                      const void* const cookie)
{
    PslInetGenericSockAddr  server;

    PSL_LOG_DEBUG("%s (%s=%p): fd=%d, addrfamily=%d, addrStr=%s, port=%d",
                  __func__, userLabel, cookie, s, addrFamily,
                  PSL_LOG_OBFUSCATE_STR(addrStr), port);

    bool success = !psl_inet_make_sock_addr(addrFamily, addrStr,
                                            port, &server, userLabel, cookie);
    if (!success) {
        return PSL_ERR_BAD_SERV_ADDR;
    }

    int rc = connect(s, (struct sockaddr*)&server.sa, server.addrLength);
    PSL_ASSERT(rc <= 0);
    if (0 == rc) {
        PSL_LOG_DEBUG("%s (%s=%p): connect() succeeded immediately",
                      __func__, userLabel, cookie);
        return 0;
    }
    else if (rc < 0 && EINPROGRESS == errno) {
        PSL_LOG_DEBUG("%s (%s=%p): connect() returned EINPROGRESS",
                      __func__, userLabel, cookie);
        return 0;
    }
    /* @todo The implication of EINTR
             when connecting on a non-blocking socket is vague, at best.
             Should we retry connect(), assume that all is well, or give up
             on this connection?
    else if (rc < 0 && EINTR == errno) {
        PSL_LOG_DEBUG("%s (fsm=%p): connect() returned EINTR",
                      __func__, pFsm);
        return 0;
    }
    */
    else { /// (rc < 0)
        int const saverrno = errno;
        PSL_LOG_ERROR("%s (%s=%p): ERROR: connect() failed; errno=%d (%s)",
                      __func__, userLabel, cookie, saverrno, strerror(saverrno));

        return psl_err_pslerror_from_connect_errno(saverrno);
    }
}//psl_inet_connect_sock
/** ========================================================================
 * =========================================================================
 */
static PslSmeEventStatus
chan_fsm_alive_state_handler(PslChanFsmAliveState*      const pState,
                             PslChanFsm*                const pFsm,
                             PslSmeEventId              const evtId,
                             const PslChanFsmEvtArg*    const evtArg)
{   
    switch (evtId) 
    {
    case kFsmEventEnterScope:
    case kFsmEventExitScope:
    case kFsmEventBegin:
        return kPslSmeEventStatus_success;
        break;

    case PSL_CHAN_FSM_EVT_CLOSE:
        psl_chan_fsm_goto_closed_state(pFsm);
        return kPslSmeEventStatus_success;
        break;

    case PSL_CHAN_FSM_EVT_FINALIZE:
        psl_chan_fsm_goto_final_state(pFsm);
        return kPslSmeEventStatus_success;
        break;

    case PSL_CHAN_FSM_EVT_CHECK_IO:
        return psl_chan_fsm_evt_make_simple_CHECK_IO_response(
            &evtArg->checkIO, kPslChanFsmEvtIOReadyHint_notReady,
            PSL_INVALID_FD);
        break;

    default:
        break;
    }

    if (evtId >= kFsmEventFirstUserEvent) {
        PSL_LOG_ERROR(
            "%s (%p): %s.%s: ERROR: Operation not allowed: evtId=%d",
            __func__, pFsm, FsmDbgPeekMachineName(&pFsm->base.base),
            FsmDbgPeekStateName(&pState->base.base), evtId);

        psl_chan_fsm_set_last_error(pFsm, kPslChanFsmErrorSource_psl,
                                    PSL_ERR_NOT_ALLOWED);
        return kPslSmeEventStatus_errorCatchAll;
    }
    else {
        return kPslSmeEventStatus_success;
    }
}//chan_fsm_alive_state_handler
/** ========================================================================
 * =========================================================================
 */
const char*
psl_chan_fsm_str_from_giostatus(GIOStatus const giostatus)
{
    switch (giostatus) {
    case G_IO_STATUS_ERROR:     return "G_IO_STATUS_ERROR";
        break;
    case G_IO_STATUS_NORMAL:    return "G_IO_STATUS_NORMAL";
        break;
    case G_IO_STATUS_EOF:       return "G_IO_STATUS_EOF";
        break;
    case G_IO_STATUS_AGAIN:     return "G_IO_STATUS_AGAIN";
        break;
    }

    PSL_LOG_ERROR("ERROR: UNKNOWN GIOStatus value: %ld", (long)giostatus);
    return "ERROR: UNKNOWN GIOStatus value (see log)";
}
/** ========================================================================
 * PslMultiFdWatchSourceCb callback from the multi-fd watch
 * source
 * 
 * @see PslMultiFdWatchSourceCb for arg info
 * 
 * @param userData
 * @param pollrecs
 * @param numrecs
 * 
 * @return gboolean
 * 
 * =========================================================================
 */
static gboolean
chan_fsm_fd_watch_cb(gpointer const userData, const PslMultiFdPollRec* pollrecs,
                     int const numrecs)
{
    PslChanFsm* const pFsm = (PslChanFsm*)userData;

    PSL_ASSERT(!pFsm->fdWatchInfo.inFdWatchCb);

    pFsm->fdWatchInfo.inFdWatchCb = true;

    PslSmeEventStatus const status =
        psl_chan_fsm_evt_dispatch_FD_WATCH(pFsm, pollrecs, numrecs);

    if (kPslSmeEventStatus_errorCatchAll == status ||
        kPslSmeEventStatus_notHandled == status) {
        PSL_LOG_ERROR("%s (fsm=%p): ERROR: FD_WATCH event fell through",
                      __func__, pFsm);
    }

    /// Process external callbacks
    struct PslChanFsmFdWatchCompletion* const cb = &pFsm->fdWatchInfo.completionCb;

    if (kPslChanFsmEvtCompletionCbId_none != cb->cb.which) {

        /// So we don't get destroyed while in callbacks
        (void)psl_chan_fsm_ref(pFsm);

        psl_chan_fsm_dispatch_completion_cb(pFsm, &cb->cb, cb->pslErr,
                                            cb->switchingToCrypto);

        cb->cb.which = kPslChanFsmEvtCompletionCbId_none;

        pFsm->fdWatchInfo.inFdWatchCb = false;

        /// okay if the FSM gets destroyed now
        psl_chan_fsm_unref(pFsm);
        return true;
    }//if user callback scheduled

    else {
        pFsm->fdWatchInfo.inFdWatchCb = false;
    }

    return true; ///< true, so the GSource won't get detached from our gmain ctx
}//chan_fsm_fd_watch_cb
Пример #10
0
/** ========================================================================
 * =========================================================================
 */
bool
psl_inet_ipaddr_from_string(const char*         const hn,
                            PslInetIPAddress*   const pBuf,
                            const char*         const userLabel,
                            const void*         const cookie)
{
    PSL_ASSERT(pBuf);

    pBuf->family = AF_UNSPEC;

    if (!hn || !*hn) {
        PSL_LOG_ERROR("%s (%s=%p): ERROR: NULL or empty hostname string",
                      __func__, userLabel, cookie);
        return false;
    }

    int             rc;

    rc = inet_pton(AF_INET, hn, &pBuf->addr);
    if (rc > 0) {
        PSL_LOG_DEBUGLOW("%s (%s=%p): hostname looks like IPv4 address",
                         __func__, userLabel, cookie);
        pBuf->family = AF_INET;
        pBuf->len = sizeof(struct in_addr);
        return true;
    }
    else {
        rc = inet_pton(AF_INET6, hn, &pBuf->addr);
        if (rc > 0) {
            PSL_LOG_DEBUGLOW("%s (%s=%p): hostname looks like IPv6 address",
                             __func__, userLabel, cookie);
            pBuf->family = AF_INET6;
            pBuf->len = sizeof(struct in6_addr);
            return true;
        }
        else {
            PSL_LOG_DEBUGLOW("%s (%s=%p): hostname string '%s' doesn't look " \
                             "like an IP address'.",
                             __func__, userLabel, cookie, PSL_LOG_OBFUSCATE_STR(hn));
        }
    }

    return false;
}//psl_inet_ipaddr_from_string
Пример #11
0
/** ========================================================================
 * =========================================================================
 */
int
psl_inet_make_sock_addr(int                       const reqFamily,
                        const char*               const addrStr,
                        int                       const port,
                        PslInetGenericSockAddr*   const res,
                        const char*               const userLabel,
                        const void*               const cookie)
{
    PSL_ASSERT(AF_INET == reqFamily || AF_INET6 == reqFamily ||
           AF_UNSPEC == reqFamily);
    PSL_ASSERT(AF_UNSPEC != reqFamily || (addrStr && addrStr[0]));
    PSL_ASSERT(res);

    memset(res, 0, sizeof(*res));

    res->family = AF_UNSPEC;


    PslInetIPAddress    pslinaddr;
    int                 inaddrlen = 0;

    int actualFamily = reqFamily;

    if (AF_UNSPEC == reqFamily) {
        PSL_LOG_DEBUGLOW("%s (%s=%p): resoloving addrfamily (was AF_UNSPEC)",
                         __func__, userLabel, cookie);
        PSL_ASSERT(addrStr);

        bool const gotAddr = psl_inet_ipaddr_from_string(addrStr, &pslinaddr,
                                                         userLabel, cookie);
        if (gotAddr) {
            actualFamily = pslinaddr.family;
            inaddrlen = pslinaddr.len;
        }
        else {
            PSL_LOG_ERROR("%s (%s=%p): ERROR: could not determine " \
                          "address family from address string '%s'.",
                          __func__, userLabel, cookie,
                          PSL_LOG_OBFUSCATE_STR(addrStr));
            return EINVAL;
        }
    }


    void*   addrDst = NULL;
    if (AF_INET == actualFamily) {
        res->addrLength = sizeof(res->sa);
        res->sa.sin_family = actualFamily;
        res->sa.sin_port = htons(port);
        res->sa.sin_addr.s_addr = INADDR_ANY;
        addrDst = &res->sa.sin_addr;
    }
    else {
        PSL_ASSERT(AF_INET6 == actualFamily);
        res->addrLength = sizeof(res->sa6);
        res->sa6.sin6_family = actualFamily;
        //res->sa6.sin6_flowinfo = 0; ///< what should we do with these?
        //res->sa6.sin6_scope_id = 0;
        res->sa6.sin6_port = htons(port);
        res->sa6.sin6_addr = in6addr_any;
        addrDst = &res->sa6.sin6_addr;
    }

    if (addrStr && inaddrlen) {
        memcpy(addrDst, &pslinaddr.addr, inaddrlen);
    }
    else if (addrStr) {
        PSL_ASSERT(addrDst);
        int const ptonRes = inet_pton(actualFamily, addrStr, addrDst);

        if (ptonRes < 0) {  /// unexpected address family - check errno
            int const saverrno = errno;
            PSL_LOG_ERROR("%s (%s=%p): ERROR: inet_pton() failed; " \
                          "family=%d, addrStr=%s, errno=%d (%s).",
                          __func__, userLabel, cookie, actualFamily,
                          PSL_LOG_OBFUSCATE_STR(addrStr), saverrno,
                          strerror(saverrno));
            PSL_ASSERT(saverrno);
            return saverrno;
        }
        else if (ptonRes == 0) {    /// addr does not match family
            /// @note This case does not set errno
            PSL_LOG_ERROR("%s (%s=%p): ERROR: inet_pton() failed; Address " \
                          "does not match family; family=%d, addrStr=%s.",
                          __func__, userLabel, cookie, actualFamily,
                          PSL_LOG_OBFUSCATE_STR(addrStr));
            return EINVAL;
        }
    }

    res->family = actualFamily;
    return 0;
}// psl_inet_make_sock_addr
/** ========================================================================
 * =========================================================================
 */
static PslSmeEventStatus
chan_fsm_init_state_handler(PslChanFsmInitState*    const pState,
                            PslChanFsm*             const pFsm,
                            PslSmeEventId           const evtId,
                            const PslChanFsmEvtArg* const evtArg)
{   
    switch (evtId) 
    {
    case kFsmEventEnterScope:
    case kFsmEventExitScope:
    case kFsmEventBegin:
        return kPslSmeEventStatus_success;
        break;

    case PSL_CHAN_FSM_EVT_CONNECT:
        if (AF_UNSPEC == pFsm->userSettings.serverAddr.addrFamily && pFsm->fd < 0) {
            PSL_LOG_ERROR("%s (fsm=%p): ERROR: PSL_CHAN_FSM_EVT_CONNECT " \
                          "failed: connect address not set", __func__, pFsm);

            psl_chan_fsm_set_last_error(pFsm, kPslChanFsmErrorSource_psl,
                                        PSL_ERR_BAD_SERV_ADDR);
            return kPslSmeEventStatus_error;
        }


        if (pFsm->fd < 0) {
            psl_chan_fsm_goto_plain_lookup_state(pFsm, &evtArg->connect.data);
        }
        else {
            static const struct PslChanFsmEvtInetAddrText addrText = {
                .family = AF_UNSPEC
                };
            psl_chan_fsm_goto_plain_conn_state(pFsm, &evtArg->connect.data, &addrText);
        }
        return kPslSmeEventStatus_success;
        break;

    case PSL_CHAN_FSM_EVT_SET_CONN_FD:
        {
            const struct PslChanFsmEvtArgSetConnFD* const arg =
                &evtArg->setConnFD;

            PSL_ASSERT(arg->fd >= 0);

            if (AF_UNSPEC != pFsm->userSettings.serverAddr.addrFamily) {
                PSL_LOG_ERROR("%s (fsm=%p): SET_CONN_FD ERROR: " \
                              "not allowed when server address is set",
                              __func__, pFsm);
                return kPslSmeEventStatus_passToParent;
            }

            if (AF_UNSPEC != pFsm->userSettings.bindAddr.addrFamily) {
                PSL_LOG_ERROR("%s (fsm=%p): SET_CONN_FD ERROR: " \
                              "not allowed when bind address is set",
                              __func__, pFsm);
                return kPslSmeEventStatus_passToParent;
            }

            if (pFsm->fd >= 0) {
                PSL_LOG_ERROR("%s (fsm=%p): SET_CONN_FD ERROR: connected " \
                              "file descriptor was already set",
                              __func__, pFsm);
                return kPslSmeEventStatus_passToParent;
            }

            pFsm->fd = arg->fd;
            pFsm->userSettings.fdIsUserProvided = true;
            pFsm->userSettings.fdOpts = arg->opts;

            return kPslSmeEventStatus_success;
            break;
        }

    case PSL_CHAN_FSM_EVT_SET_SERVER:
        {
            const struct PslChanFsmEvtArgSetServer* const arg =
                &evtArg->setServer;

            if (pFsm->fd >= 0) {
                PSL_LOG_ERROR("%s (fsm=%p): SET_SERVER ADDR ERROR: not " \
                              "allowed when connected file descriptor is set",
                              __func__, pFsm);
                return kPslSmeEventStatus_passToParent;
            }

            if (AF_UNSPEC != pFsm->userSettings.serverAddr.addrFamily) {
                PSL_LOG_ERROR("%s (fsm=%p): SET_SERVER ADDR ERROR: " \
                              "server address was already set",
                              __func__, pFsm);
                return kPslSmeEventStatus_passToParent;
            }

            PSL_LOG_DEBUG("%s (fsm=%p): SET_SERVER ADDR: server: '%s:%d'/af=%d",
                          __func__, pFsm,
                          PSL_LOG_OBFUSCATE_STR(arg->hostStr),
                          (int)arg->port,
                          (int)arg->addrFamily);

            pFsm->userSettings.serverAddr.addrFamily = arg->addrFamily;
            pFsm->userSettings.serverAddr.port = arg->port;
            pFsm->userSettings.serverAddr.hostStr = g_strdup(arg->hostStr);
            PSL_ASSERT(pFsm->userSettings.serverAddr.hostStr);
        }
        return kPslSmeEventStatus_success;
        break;

    case PSL_CHAN_FSM_EVT_SET_SOCK_BIND:
        {
            const struct PslChanFsmEvtArgSetSockBind* const arg =
                &evtArg->setSockBind;

            if (pFsm->fd >= 0) {
                PSL_LOG_ERROR("%s (fsm=%p): SET_SOCK_BIND ERROR: not " \
                              "allowed when connected file descriptor is set",
                              __func__, pFsm);
                return kPslSmeEventStatus_passToParent;
            }

            if (AF_UNSPEC != pFsm->userSettings.bindAddr.addrFamily) {
                PSL_LOG_ERROR("%s (fsm=%p): SET_SOCK_BIND ERROR: " \
                              "socket bind address was already set",
                              __func__, pFsm);
                return kPslSmeEventStatus_passToParent;
            }

            PSL_LOG_DEBUG("%s (fsm=%p): SET_SOCK_BIND: local addr: '%s:%d'/af=%d",
                          __func__, pFsm,
                          PSL_LOG_MAKE_SAFE_STR(arg->ipAddrStr),
                          (int)arg->port,
                          (int)arg->addrFamily);

            pFsm->userSettings.bindAddr.addrFamily = arg->addrFamily;
            pFsm->userSettings.bindAddr.port = arg->port;
            pFsm->userSettings.bindAddr.hostStr = g_strdup(arg->ipAddrStr);
            PSL_ASSERT(!arg->ipAddrStr || pFsm->userSettings.bindAddr.hostStr);
        }
        return kPslSmeEventStatus_success;
        break;

    default:
        break; ///< allow default processing by parent
    }

    return kPslSmeEventStatus_passToParent;
}//chan_fsm_init_state_handler
Пример #13
0
/** ========================================================================
 * The 'dispatch' function of GSourceFuncs
 * 
 * @param base
 * @param opaqueCb @see GSourceFuncs
 * @param userData @see GSourceFuncs
 * 
 * @return gboolean @see GSourceFuncs
 * 
 * =========================================================================
 */
static gboolean
multi_fd_watch_dispatch(GSource* const base,
                        GSourceFunc opaqueCb,
                        gpointer userData)
{
    PslMultiFdWatchSource* const source = (PslMultiFdWatchSource*)base;
    PslMultiFdWatchSourceCb* const cb = (PslMultiFdWatchSourceCb*)opaqueCb;

    source->restartTimer = true; ///< so it will restart at next poll prepare
    
    if (!cb) {
        PSL_LOG_ERROR("%s (watch=%p): ERROR: multi-fd watch dispatch with " \
                      "NULL user callback ptr: did you forget to call " \
                      "g_source_set_callback()?", __func__, base);
        return false;
    }

    PSL_LOG_DEBUGLOW("%s (watch=%p): preparing to call user's callback",
                     __func__, base);

    /// Construct an array of ready PollFD's
    if (source->cachedReadyPollFdArray->len > 0) {
        g_array_set_size(source->cachedReadyPollFdArray, 0);
    }

    gpointer key, value;
    GHashTableIter iter;
    g_hash_table_iter_init(&iter, source->descTable);
    while (g_hash_table_iter_next(&iter, &key, &value)) {
        const GPollFD* const pollFd = &((PslMultiFdDescriptor*)value)->pollFd;

        PSL_LOG_DEBUGLOW(
            "%s (watch=%p): iter: table=%p, desc=%p, fd=%d, " \
            "GIOCondition=(mon:0x%lX, ind:0x%lX)",
            __func__, source, source->descTable, value, (int)pollFd->fd,
            (unsigned long)pollFd->events, (unsigned long)pollFd->revents);

        if ((pollFd->revents & MULTI_FD_WATCH_PERM_FAILURE_IO_CONDS) != 0) {
            PSL_LOG_ERROR("%s: (watch=%p): I/O FAILURE on fd=%d: indicated " \
                          "GIOCondition=0x%lX", __func__, source,
                          (int)pollFd->fd, (unsigned long)pollFd->revents);
        }


        const GIOCondition currentCondition = pollFd->revents &
            (pollFd->events | MULTI_FD_WATCH_PERM_FAILURE_IO_CONDS);
        if (currentCondition) {
            const PslMultiFdPollRec pollRec = {
                .fd = pollFd->fd,
                .reqEvents = pollFd->events,
                .indEvents = currentCondition
            };

            g_array_append_vals(source->cachedReadyPollFdArray, &pollRec, 1);
        }
    }

    /// Dispatch the callback
    const gint numrecs = source->cachedReadyPollFdArray->len;
    const PslMultiFdPollRec* const pollrecs  =
        ((numrecs > 0)
         ? (PslMultiFdPollRec*)source->cachedReadyPollFdArray->data
         : NULL);

    PSL_LOG_DEBUGLOW("%s (watch=%p): Calling user's callback: " \
                     "pollrec array=%p, numelts=%d",
                     __func__, base, pollrecs, (int)numrecs);

    const gboolean stayAttached = cb(userData, pollrecs, numrecs);

    if (!stayAttached) {
        PSL_LOG_DEBUG("%s (watch=%p): user cb requested removal of source",
                      __func__, base);
    }
    return stayAttached;
} //multi_fd_watch_dispatch



/** ========================================================================
 * The 'finalize' function of GSourceFuncs
 * 
 * @param base
 * 
 * =========================================================================
 */
static void
multi_fd_watch_finalize(GSource* base)
{
    PSL_LOG_DEBUG("%s (watch=%p)", __func__, base);

    PslMultiFdWatchSource* const source = (PslMultiFdWatchSource*)base;

    /**
     * @note We may be called with our own data members in a
     *       partially-constructed (but initialized) state if
     *       psl_multi_fd_watch_new failed part way through and
     *       called g_source_unref.
     */

    if (source->descTable) {
        /**
         * @note our "superclass" GSource takes care of removing
         *       our pollFds record pointers from the source's and the
         *       gmain context's pollFd lists when the GSource instance
         *       is being detached from the gmain context, so we don't
         *       need to do it ourselves.
         */

        g_hash_table_destroy(source->descTable);
    }

    if (source->cachedReadyPollFdArray) {
        (void)g_array_free(source->cachedReadyPollFdArray,
                           true/*free_segment*/);
    }
}