mirror of
https://github.com/linux-msm/qrtr.git
synced 2026-04-09 23:00:03 +02:00
waiter_wait_timeout() doesn't initialize the 'callback' and 'updated' fields of the waiter_ticket struct. The 'updated' field may contain some garbage value, which is later read in waiter_ticket_check(). Signed-off-by: Ben Chan <benchan@chromium.org>
379 lines
8.0 KiB
C
379 lines
8.0 KiB
C
/*
|
|
* Copyright (c) 2013-2014, Sony Mobile Communications Inc.
|
|
* Copyright (c) 2014, Courtney Cavin
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* - Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* - Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* - Neither the name of the organization nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <poll.h>
|
|
|
|
#include "list.h"
|
|
#include "waiter.h"
|
|
#include "util.h"
|
|
|
|
struct pollset {
|
|
int nfds;
|
|
int cause;
|
|
};
|
|
|
|
static struct pollset *pollset_create(int count)
|
|
{
|
|
struct pollset *ps;
|
|
|
|
ps = calloc(1, sizeof(*ps) + sizeof(struct pollfd) * count);
|
|
if (ps == NULL)
|
|
return NULL;
|
|
|
|
return ps;
|
|
}
|
|
|
|
static void pollset_destroy(struct pollset *ps)
|
|
{
|
|
free(ps);
|
|
}
|
|
|
|
static void pollset_reset(struct pollset *ps)
|
|
{
|
|
ps->nfds = 0;
|
|
}
|
|
|
|
static void pollset_add_fd(struct pollset *ps, int fd)
|
|
{
|
|
struct pollfd *pfd = (struct pollfd *)(ps + 1);
|
|
pfd[ps->nfds].fd = fd;
|
|
pfd[ps->nfds].events = POLLERR | POLLIN;
|
|
ps->nfds++;
|
|
}
|
|
|
|
static int pollset_wait(struct pollset *ps, int ms)
|
|
{
|
|
struct pollfd *pfd = (struct pollfd *)(ps + 1);
|
|
int rc;
|
|
int i;
|
|
|
|
rc = poll(pfd, ps->nfds, ms);
|
|
if (rc <= 0)
|
|
return rc;
|
|
|
|
ps->cause = -1;
|
|
for (i = 0; i < ps->nfds; ++i) {
|
|
if (pfd[i].revents & (POLLERR | POLLIN)) {
|
|
ps->cause = i;
|
|
break;
|
|
}
|
|
}
|
|
return rc;
|
|
|
|
}
|
|
|
|
static int pollset_cause_fd(struct pollset *ps, int fd)
|
|
{
|
|
struct pollfd *pfd = (struct pollfd *)(ps + 1);
|
|
return (ps->cause >= 0 && pfd[ps->cause].fd == fd);
|
|
}
|
|
|
|
enum waiter_type {
|
|
WATCH_TYPE_NULL,
|
|
WATCH_TYPE_FD,
|
|
WATCH_TYPE_TIMEOUT,
|
|
};
|
|
|
|
struct waiter_ticket {
|
|
enum waiter_type type;
|
|
union {
|
|
int filedes;
|
|
unsigned int event;
|
|
unsigned int interval;
|
|
};
|
|
struct {
|
|
void (* fn)(void *data, struct waiter_ticket *);
|
|
void *data;
|
|
} callback;
|
|
|
|
uint64_t start;
|
|
int updated;
|
|
struct waiter *waiter;
|
|
struct list_item list_item;
|
|
};
|
|
|
|
struct waiter {
|
|
struct list tickets;
|
|
struct pollset *pollset;
|
|
int count;
|
|
};
|
|
|
|
struct waiter *waiter_create(void)
|
|
{
|
|
struct waiter *w;
|
|
|
|
w = calloc(1, sizeof(*w));
|
|
if (w == NULL)
|
|
return NULL;
|
|
|
|
list_init(&w->tickets);
|
|
return w;
|
|
}
|
|
|
|
void waiter_destroy(struct waiter *w)
|
|
{
|
|
struct waiter_ticket *ticket;
|
|
struct list_item *safe;
|
|
struct list_item *node;
|
|
|
|
list_for_each_safe(&w->tickets, node, safe) {
|
|
ticket = list_entry(node, struct waiter_ticket, list_item);
|
|
free(ticket);
|
|
}
|
|
|
|
if (w->pollset)
|
|
pollset_destroy(w->pollset);
|
|
free(w);
|
|
}
|
|
|
|
void waiter_synchronize(struct waiter *w)
|
|
{
|
|
struct waiter_ticket *oticket;
|
|
struct waiter_ticket *ticket;
|
|
struct list_item *node;
|
|
|
|
list_for_each(&w->tickets, node) {
|
|
struct list_item *onode;
|
|
ticket = list_entry(node, struct waiter_ticket, list_item);
|
|
|
|
if (ticket->type != WATCH_TYPE_TIMEOUT)
|
|
continue;
|
|
|
|
list_for_each_after(node, onode) {
|
|
oticket = list_entry(onode, struct waiter_ticket, list_item);
|
|
if (oticket->type != WATCH_TYPE_TIMEOUT)
|
|
continue;
|
|
|
|
if (oticket->interval == ticket->interval) {
|
|
oticket->start = ticket->start;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void waiter_wait(struct waiter *w)
|
|
{
|
|
struct pollset *ps = w->pollset;
|
|
struct waiter_ticket *ticket;
|
|
struct list_item *node;
|
|
uint64_t term_time;
|
|
uint64_t now;
|
|
int rc;
|
|
|
|
pollset_reset(ps);
|
|
|
|
term_time = (uint64_t)-1;
|
|
list_for_each(&w->tickets, node) {
|
|
ticket = list_entry(node, struct waiter_ticket, list_item);
|
|
switch (ticket->type) {
|
|
case WATCH_TYPE_TIMEOUT:
|
|
if (ticket->start + ticket->interval < term_time)
|
|
term_time = ticket->start + ticket->interval;
|
|
break;
|
|
case WATCH_TYPE_FD:
|
|
pollset_add_fd(ps, ticket->filedes);
|
|
break;
|
|
case WATCH_TYPE_NULL:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (term_time == (uint64_t)-1) { /* wait forever */
|
|
rc = pollset_wait(ps, -1);
|
|
} else {
|
|
now = time_ms();
|
|
if (now >= term_time) { /* already past timeout, skip poll */
|
|
rc = 0;
|
|
} else {
|
|
uint64_t delta;
|
|
|
|
delta = term_time - now;
|
|
if (delta > ((1u << 31) - 1))
|
|
delta = ((1u << 31) - 1);
|
|
rc = pollset_wait(ps, (int)delta);
|
|
}
|
|
}
|
|
|
|
if (rc < 0)
|
|
return;
|
|
|
|
now = time_ms();
|
|
list_for_each(&w->tickets, node) {
|
|
int fresh = 0;
|
|
|
|
ticket = list_entry(node, struct waiter_ticket, list_item);
|
|
switch (ticket->type) {
|
|
case WATCH_TYPE_TIMEOUT:
|
|
if (now >= ticket->start + ticket->interval) {
|
|
ticket->start = now;
|
|
fresh = !ticket->updated;
|
|
}
|
|
break;
|
|
case WATCH_TYPE_FD:
|
|
if (rc == 0) /* timed-out */
|
|
break;
|
|
if (pollset_cause_fd(ps, ticket->filedes))
|
|
fresh = !ticket->updated;
|
|
break;
|
|
case WATCH_TYPE_NULL:
|
|
break;
|
|
}
|
|
if (fresh) {
|
|
ticket->updated = 1;
|
|
if (ticket->callback.fn)
|
|
(* ticket->callback.fn)(
|
|
ticket->callback.data,
|
|
ticket
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
int waiter_wait_timeout(struct waiter *w, unsigned int ms)
|
|
{
|
|
struct waiter_ticket ticket;
|
|
int rc;
|
|
|
|
memset(&ticket, 0, sizeof(ticket));
|
|
waiter_ticket_set_timeout(&ticket, ms);
|
|
list_append(&w->tickets, &ticket.list_item);
|
|
w->count++;
|
|
|
|
waiter_wait(w);
|
|
rc = waiter_ticket_check(&ticket);
|
|
|
|
list_remove(&w->tickets, &ticket.list_item);
|
|
w->count--;
|
|
|
|
return -!rc;
|
|
}
|
|
|
|
void waiter_ticket_set_null(struct waiter_ticket *ticket)
|
|
{
|
|
ticket->type = WATCH_TYPE_NULL;
|
|
}
|
|
|
|
void waiter_ticket_set_fd(struct waiter_ticket *ticket, int fd)
|
|
{
|
|
ticket->type = WATCH_TYPE_FD;
|
|
ticket->filedes = fd;
|
|
}
|
|
|
|
void waiter_ticket_set_timeout(struct waiter_ticket *ticket, unsigned int ms)
|
|
{
|
|
ticket->type = WATCH_TYPE_TIMEOUT;
|
|
ticket->interval = ms;
|
|
ticket->start = time_ms();
|
|
}
|
|
|
|
struct waiter_ticket *waiter_add_null(struct waiter *w)
|
|
{
|
|
struct waiter_ticket *ticket;
|
|
|
|
ticket = calloc(1, sizeof(*ticket));
|
|
if (ticket == NULL)
|
|
return NULL;
|
|
ticket->waiter = w;
|
|
|
|
list_append(&w->tickets, &ticket->list_item);
|
|
if ((w->count % 32) == 0) {
|
|
if (w->pollset)
|
|
pollset_destroy(w->pollset);
|
|
w->pollset = pollset_create(w->count + 33);
|
|
if (w->pollset == NULL)
|
|
return NULL;
|
|
}
|
|
w->count++;
|
|
|
|
waiter_ticket_set_null(ticket);
|
|
|
|
return ticket;
|
|
}
|
|
|
|
struct waiter_ticket *waiter_add_fd(struct waiter *w, int fd)
|
|
{
|
|
struct waiter_ticket *ticket;
|
|
|
|
ticket = waiter_add_null(w);
|
|
if (ticket == NULL)
|
|
return NULL;
|
|
|
|
waiter_ticket_set_fd(ticket, fd);
|
|
|
|
return ticket;
|
|
}
|
|
|
|
struct waiter_ticket *waiter_add_timeout(struct waiter *w, unsigned int ms)
|
|
{
|
|
struct waiter_ticket *ticket;
|
|
|
|
ticket = waiter_add_null(w);
|
|
if (ticket == NULL)
|
|
return NULL;
|
|
|
|
waiter_ticket_set_timeout(ticket, ms);
|
|
|
|
return ticket;
|
|
}
|
|
|
|
void waiter_ticket_delete(struct waiter_ticket *ticket)
|
|
{
|
|
struct waiter *w = ticket->waiter;
|
|
list_remove(&w->tickets, &ticket->list_item);
|
|
w->count--;
|
|
free(ticket);
|
|
}
|
|
|
|
void waiter_ticket_callback(struct waiter_ticket *ticket, waiter_ticket_cb_t cb_fn, void *data)
|
|
{
|
|
ticket->callback.fn = cb_fn;
|
|
ticket->callback.data = data;
|
|
}
|
|
|
|
int waiter_ticket_check(const struct waiter_ticket *ticket)
|
|
{
|
|
return -(ticket->updated == 0);
|
|
}
|
|
|
|
int waiter_ticket_clear(struct waiter_ticket *ticket)
|
|
{
|
|
int ret;
|
|
|
|
ret = waiter_ticket_check(ticket);
|
|
ticket->updated = 0;
|
|
|
|
return ret;
|
|
}
|