2 * event.c - ACPI daemon event handler
4 * Copyright (C) 2000 Andrew Henroid
5 * Copyright (C) 2001 Sun Microsystems (thockin@sun.com)
6 * Copyright (C) 2004 Tim Hockin (thockin@hockin.org)
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #include <sys/types.h>
41 #include "ud_socket.h"
44 * What is a rule? It's polymorphic, pretty much.
46 #define RULE_REGEX_FLAGS (REG_EXTENDED | REG_ICASE | REG_NOSUB | REG_NEWLINE)
66 static struct rule_list cmd_list;
67 static struct rule_list client_list;
70 static void enlist_rule(struct rule_list *list, struct rule *r);
71 static void delist_rule(struct rule_list *list, struct rule *r);
72 static struct rule *new_rule(void);
73 static void free_rule(struct rule *r);
75 /* other helper routines */
76 static void lock_rules(void);
77 static void unlock_rules(void);
78 static sigset_t *signals_handled(void);
79 static struct rule *parse_file(const char *file);
80 static struct rule *parse_client(int client);
81 static int do_cmd_rule(struct rule *r, const char *event);
82 static int do_client_rule(struct rule *r, const char *event);
83 static int safe_write(int fd, const char *buf, int len);
84 static char *parse_cmd(const char *cmd, const char *event);
85 static int check_escapes(const char *str);
88 * read in all the configuration files
91 acpid_read_conf(const char *confdir)
94 struct dirent *dirent;
102 dir = opendir(confdir);
104 acpid_log(LOG_ERR, "opendir(%s): %s",
105 confdir, strerror(errno));
110 /* Compile the regular expression. This is based on run-parts(8). */
111 rc = regcomp(&preg, "^[a-zA-Z0-9_-]+$", RULE_REGEX_FLAGS);
113 acpid_log(LOG_ERR, "regcomp(): %d", rc);
118 /* scan all the files */
119 while ((dirent = readdir(dir))) {
122 struct stat stat_buf;
124 len = strlen(dirent->d_name);
126 /* skip "." and ".." */
127 if (strncmp(dirent->d_name, ".", sizeof(dirent->d_name)) == 0)
129 if (strncmp(dirent->d_name, "..", sizeof(dirent->d_name)) == 0)
132 /* skip any files that don't match the run-parts convention */
133 if (regexec(&preg, dirent->d_name, 0, NULL, 0) != 0) {
134 acpid_log(LOG_INFO, "skipping conf file %s/%s",
135 confdir, dirent->d_name);
139 /* Compute the length of the full path name adding one for */
140 /* the slash and one more for the NULL. */
141 len += strlen(confdir) + 2;
145 acpid_log(LOG_ERR, "malloc(): %s", strerror(errno));
149 snprintf(file, len, "%s/%s", confdir, dirent->d_name);
151 /* allow only regular files and symlinks to files */
152 if (stat(file, &stat_buf) != 0) {
153 acpid_log(LOG_ERR, "stat(%s): %s", file,
156 continue; /* keep trying the rest of the files */
158 if (!S_ISREG(stat_buf.st_mode)) {
159 acpid_log(LOG_INFO, "skipping non-file %s", file);
161 continue; /* skip non-regular files */
164 r = parse_file(file);
166 enlist_rule(&cmd_list, r);
174 acpid_log(LOG_INFO, "%d rule%s loaded",
175 nrules, (nrules == 1)?"":"s");
184 acpid_cleanup_rules(int do_detach)
191 if (acpid_debug >= 3) {
192 acpid_log(LOG_DEBUG, "cleaning up rules");
196 /* tell our clients to buzz off */
197 p = client_list.head;
200 delist_rule(&client_list, p);
207 /* clear out our conf rules */
211 delist_rule(&cmd_list, p);
222 parse_file(const char *file)
229 acpid_log(LOG_DEBUG, "parsing conf file %s", file);
231 fp = fopen(file, "r");
233 acpid_log(LOG_ERR, "fopen(%s): %s", file, strerror(errno));
237 /* make a new rule */
244 r->origin = strdup(file);
246 acpid_log(LOG_ERR, "strdup(): %s", strerror(errno));
253 while (!feof(fp) && !ferror(fp)) {
260 memset(key, 0, sizeof(key));
261 memset(val, 0, sizeof(val));
263 if (fgets(buf, sizeof(buf)-1, fp) == NULL) {
267 /* skip leading whitespace */
268 while (*p && isspace((int)*p)) {
271 /* blank lines and comments get ignored */
272 if (!*p || *p == '#') {
277 n = sscanf(p, "%63[^=\n]=%255[^\n]", key, val);
279 acpid_log(LOG_WARNING, "can't parse %s at line %d",
283 if (acpid_debug >= 3) {
284 acpid_log(LOG_DEBUG, " key=\"%s\" val=\"%s\"",
287 /* handle the parsed line */
288 if (!strcasecmp(key, "event")) {
290 r->event = malloc(sizeof(regex_t));
292 acpid_log(LOG_ERR, "malloc(): %s",
298 rv = regcomp(r->event, val, RULE_REGEX_FLAGS);
301 regerror(rv, r->event, rbuf, sizeof(rbuf));
302 acpid_log(LOG_ERR, "regcomp(): %s", rbuf);
307 } else if (!strcasecmp(key, "action")) {
308 if (check_escapes(val) < 0) {
309 acpid_log(LOG_ERR, "can't load file %s",
315 r->action.cmd = strdup(val);
316 if (!r->action.cmd) {
317 acpid_log(LOG_ERR, "strdup(): %s",
324 acpid_log(LOG_WARNING,
325 "unknown option '%s' in %s at line %d",
330 if (!r->event || !r->action.cmd) {
331 acpid_log(LOG_INFO, "skipping incomplete file %s", file);
342 acpid_add_client(int clifd, const char *origin)
347 acpid_log(LOG_NOTICE, "client connected from %s", origin);
349 r = parse_client(clifd);
351 r->origin = strdup(origin);
352 enlist_rule(&client_list, r);
356 acpid_log(LOG_INFO, "%d client rule%s loaded",
357 nrules, (nrules == 1)?"":"s");
363 parse_client(int client)
368 /* make a new rule */
373 r->type = RULE_CLIENT;
374 r->action.fd = client;
375 r->event = malloc(sizeof(regex_t));
377 acpid_log(LOG_ERR, "malloc(): %s", strerror(errno));
381 rv = regcomp(r->event, ".*", RULE_REGEX_FLAGS);
384 regerror(rv, r->event, buf, sizeof(buf));
385 acpid_log(LOG_ERR, "regcomp(): %s", buf);
398 enlist_rule(struct rule_list *list, struct rule *r)
400 r->next = r->prev = NULL;
402 list->head = list->tail = r;
404 list->tail->next = r;
405 r->prev = list->tail;
411 delist_rule(struct rule_list *list, struct rule *r)
414 r->next->prev = r->prev;
416 list->tail = r->prev;
420 r->prev->next = r->next;
422 list->head = r->next;;
425 r->next = r->prev = NULL;
433 r = malloc(sizeof(*r));
435 acpid_log(LOG_ERR, "malloc(): %s", strerror(errno));
442 r->action.cmd = NULL;
443 r->prev = r->next = NULL;
448 /* I hope you delisted the rule before you free() it */
450 free_rule(struct rule *r)
452 if (r->type == RULE_CMD) {
470 client_is_dead(int fd)
475 /* check the fd to see if it is dead */
477 pfd.events = POLLERR | POLLHUP;
478 r = poll(&pfd, 1, 0);
481 acpid_log(LOG_ERR, "poll(): %s", strerror(errno));
489 acpid_close_dead_clients(void)
495 /* scan our client list */
496 p = client_list.head;
498 struct rule *next = p->next;
499 if (client_is_dead(p->action.fd)) {
502 acpid_log(LOG_NOTICE,
503 "client %s has disconnected", p->origin);
504 delist_rule(&client_list, p);
505 ud_get_peercred(p->action.fd, &cred);
519 * the main hook for propogating events
522 acpid_handle_event(const char *event)
526 struct rule_list *ar[] = { &client_list, &cmd_list, NULL };
527 struct rule_list **lp;
529 /* make an event be atomic wrt known signals */
532 /* scan each rule list for any rules that care about this event */
533 for (lp = ar; *lp; lp++) {
534 struct rule_list *l = *lp;
537 /* the list can change underneath us */
538 struct rule *pnext = p->next;
539 if (!regexec(p->event, event, 0, NULL, 0)) {
543 "rule from %s matched",
547 if (p->type == RULE_CMD) {
548 do_cmd_rule(p, event);
549 } else if (p->type == RULE_CLIENT) {
550 do_client_rule(p, event);
552 acpid_log(LOG_WARNING,
553 "unknown rule type: %d",
557 if (acpid_debug >= 3 && logevents) {
559 "rule from %s did not match",
570 acpid_log(LOG_INFO, "%d total rule%s matched",
571 nrules, (nrules == 1)?"":"s");
577 /* helper functions to block signals while iterating */
579 signals_handled(void)
584 sigaddset(&set, SIGHUP);
585 sigaddset(&set, SIGTERM);
586 sigaddset(&set, SIGQUIT);
587 sigaddset(&set, SIGINT);
595 if (acpid_debug >= 4) {
596 acpid_log(LOG_DEBUG, "blocking signals for rule lock");
598 sigprocmask(SIG_BLOCK, signals_handled(), NULL);
604 if (acpid_debug >= 4) {
605 acpid_log(LOG_DEBUG, "unblocking signals for rule lock");
607 sigprocmask(SIG_UNBLOCK, signals_handled(), NULL);
611 * the meat of the rules
615 do_cmd_rule(struct rule *rule, const char *event)
624 acpid_log(LOG_ERR, "fork(): %s", strerror(errno));
627 /* parse the commandline, doing any substitutions needed */
628 action = parse_cmd(rule->action.cmd, event);
631 "executing action \"%s\"", action);
635 signal(SIGHUP, SIG_DFL);
636 signal(SIGTERM, SIG_DFL);
637 signal(SIGINT, SIG_DFL);
638 signal(SIGQUIT, SIG_DFL);
639 signal(SIGPIPE, SIG_DFL);
640 sigprocmask(SIG_UNBLOCK, signals_handled(), NULL);
642 if (acpid_debug && logevents) {
643 fprintf(stdout, "BEGIN HANDLER MESSAGES\n");
646 execl("/bin/sh", "/bin/sh", "-c", action, NULL);
647 /* should not get here */
648 acpid_log(LOG_ERR, "execl(): %s", strerror(errno));
653 waitpid(pid, &status, 0);
654 if (acpid_debug && logevents) {
655 fprintf(stdout, "END HANDLER MESSAGES\n");
659 if (WIFEXITED(status)) {
660 acpid_log(LOG_INFO, "action exited with status %d",
661 WEXITSTATUS(status));
662 } else if (WIFSIGNALED(status)) {
663 acpid_log(LOG_INFO, "action exited on signal %d",
666 acpid_log(LOG_INFO, "action exited with status %d",
675 do_client_rule(struct rule *rule, const char *event)
678 int client = rule->action.fd;
681 acpid_log(LOG_INFO, "notifying client %s", rule->origin);
684 r = safe_write(client, event, strlen(event));
685 if (r < 0 && errno == EPIPE) {
688 acpid_log(LOG_NOTICE,
689 "client %s has disconnected", rule->origin);
690 delist_rule(&client_list, rule);
691 ud_get_peercred(rule->action.fd, &cred);
695 close(rule->action.fd);
699 safe_write(client, "\n", 1);
706 safe_write(int fd, const char *buf, int len)
713 r = write(fd, buf+ttl, len-ttl);
715 if (errno != EAGAIN && errno != EINTR) {
721 /* as long as we make forward progress, reset ntries */
725 } while (ttl < len && ntries);
728 if (acpid_debug >= 2) {
729 acpid_log(LOG_ERR, "safe_write() timed out");
738 parse_cmd(const char *cmd, const char *event)
740 static char buf[4096];
747 memset(buf, 0, sizeof(buf));
748 while (i < (sizeof(buf)-1)) {
752 /* handle an event expansion */
753 size_t size = sizeof(buf) - i;
754 size = snprintf(buf+i, size, "%s", event);
765 if (acpid_debug >= 2) {
766 acpid_log(LOG_DEBUG, "expanded \"%s\" -> \"%s\"", cmd, buf);
773 check_escapes(const char *str)
780 /* found an escape */
784 acpid_log(LOG_WARNING,
785 "invalid escape at EOL");
787 } else if (*p != '%' && *p != 'e') {
788 acpid_log(LOG_WARNING,
789 "invalid escape \"%%%c\"", *p);