/* ----------------------------------------------------------------------- *
*
* alarm.c - alarm queue handling module.
*
* Copyright 2006 Ian Kent <raven@themaw.net> - All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, Inc., 675 Mass Ave, Cambridge MA 02139,
* USA; either version 2 of the License, or (at your option) any later
* version; incorporated herein by reference.
*
* ----------------------------------------------------------------------- */
#include <stdlib.h>
#include "automount.h"
struct alarm {
time_t time;
unsigned int cancel;
struct autofs_point *ap;
struct list_head list;
};
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond;
static LIST_HEAD(alarms);
#define alarm_lock() \
do { \
int _alm_lock = pthread_mutex_lock(&mutex); \
if (_alm_lock) \
fatal(_alm_lock); \
} while (0)
#define alarm_unlock() \
do { \
int _alm_unlock = pthread_mutex_unlock(&mutex); \
if (_alm_unlock) \
fatal(_alm_unlock); \
} while (0)
/* Insert alarm entry on ordered list. */
int alarm_add(struct autofs_point *ap, time_t seconds)
{
struct list_head *head;
struct list_head *p;
struct alarm *new;
time_t now = monotonic_time(NULL);
time_t next_alarm = 0;
unsigned int empty = 1;
int status;
if (!seconds)
return 1;
new = malloc(sizeof(struct alarm));
if (!new)
return 0;
new->ap = ap;
new->cancel = 0;
new->time = now + seconds;
alarm_lock();
head = &alarms;
/* Check if we have a pending alarm */
if (!list_empty(head)) {
struct alarm *current;
current = list_entry(head->next, struct alarm, list);
next_alarm = current->time;
empty = 0;
}
list_for_each(p, head) {
struct alarm *this;
this = list_entry(p, struct alarm, list);
if (this->time >= new->time) {
list_add_tail(&new->list, p);
break;
}
}
if (p == head)
list_add_tail(&new->list, p);
/*
* Wake the alarm thread if it is not busy (ie. if the
* alarms list was empty) or if the new alarm comes before
* the alarm we are currently waiting on.
*/
if (empty || new->time < next_alarm) {
status = pthread_cond_signal(&cond);
if (status)
fatal(status);
}
alarm_unlock();
return 1;
}
void alarm_delete(struct autofs_point *ap)
{
struct list_head *head;
struct list_head *p;
struct alarm *current;
unsigned int signal_cancel = 0;
int status;
alarm_lock();
head = &alarms;
if (list_empty(head)) {
alarm_unlock();
return;
}
current = list_entry(head->next, struct alarm, list);
p = head->next;
while (p != head) {
struct alarm *this;
this = list_entry(p, struct alarm, list);
p = p->next;
if (ap == this->ap) {
if (current != this) {
list_del_init(&this->list);
free(this);
continue;
}
/* Mark as canceled */
this->cancel = 1;
this->time = 0;
signal_cancel = 1;
}
}
if (signal_cancel) {
status = pthread_cond_signal(&cond);
if (status)
fatal(status);
}
alarm_unlock();
return;
}
static void *alarm_handler(void *arg)
{
struct list_head *head;
struct timespec expire;
struct alarm *first;
time_t now;
int status;
alarm_lock();
head = &alarms;
while (1) {
if (list_empty(head)) {
/* No alarms, wait for one to be added */
status = pthread_cond_wait(&cond, &mutex);
if (status)
fatal(status);
continue;
}
first = list_entry(head->next, struct alarm, list);
now = monotonic_time(NULL);
if (first->time > now) {
struct timespec nsecs;
/*
* Wait for alarm to trigger or a new alarm
* to be added.
*/
clock_gettime(CLOCK_MONOTONIC, &nsecs);
expire.tv_sec = first->time;
expire.tv_nsec = nsecs.tv_nsec;
status = pthread_cond_timedwait(&cond, &mutex, &expire);
if (status && status != ETIMEDOUT)
fatal(status);
} else {
/* First alarm has triggered, run it */
list_del(&first->list);
if (!first->cancel) {
struct autofs_point *ap = first->ap;
alarm_unlock();
st_add_task(ap, ST_EXPIRE);
alarm_lock();
}
free(first);
}
}
/* Will never come here, so alarm_unlock is not necessary */
}
int alarm_start_handler(void)
{
pthread_t thid;
pthread_attr_t attrs;
pthread_attr_t *pattrs = &attrs;
pthread_condattr_t condattrs;
int status;
status = pthread_attr_init(pattrs);
if (status)
pattrs = NULL;
else {
pthread_attr_setdetachstate(pattrs, PTHREAD_CREATE_DETACHED);
#ifdef _POSIX_THREAD_ATTR_STACKSIZE
pthread_attr_setstacksize(pattrs, PTHREAD_STACK_MIN*4);
#endif
}
status = pthread_condattr_init(&condattrs);
if (status)
fatal(status);
status = pthread_condattr_setclock(&condattrs, CLOCK_MONOTONIC);
if (status)
fatal(status);
status = pthread_cond_init(&cond, &condattrs);
if (status)
fatal(status);
status = pthread_create(&thid, pattrs, alarm_handler, NULL);
pthread_condattr_destroy(&condattrs);
if (pattrs)
pthread_attr_destroy(pattrs);
return !status;
}