Main Page | Class List | File List | Class Members | File Members

event.c

Go to the documentation of this file.
00001 /*
00002  *  event.c - ACPI daemon
00003  *
00004  *  Copyright (C) 2000 Andrew Henroid
00005  *  Copyright (C) 2001 Sun Microsystems (thockin@sun.com)
00006  *
00007  *  This program is free software; you can redistribute it and/or modify
00008  *  it under the terms of the GNU General Public License as published by
00009  *  the Free Software Foundation; either version 2 of the License, or
00010  *  (at your option) any later version.
00011  *
00012  *  This program is distributed in the hope that it will be useful,
00013  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  *  GNU General Public License for more details.
00016  *
00017  *  You should have received a copy of the GNU General Public License
00018  *  along with this program; if not, write to the Free Software
00019  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00020  */
00021 
00022 #include <sys/types.h>
00023 #include <sys/stat.h>
00024 #include <sys/wait.h>
00025 #include <fcntl.h>
00026 #include <unistd.h>
00027 #include <stdio.h>
00028 #include <stdlib.h>
00029 #include <string.h>
00030 #include <errno.h>
00031 #include <dirent.h>
00032 #include <ctype.h>
00033 #include <regex.h>
00034 #include <signal.h>
00035 
00036 #include "acpid.h"
00037 
00038 /*
00039  * What is a rule?  It's polymorphic, pretty much.
00040  */
00041 #define RULE_REGEX_FLAGS (REG_EXTENDED | REG_ICASE | REG_NOSUB | REG_NEWLINE)
00042 struct rule {
00043         enum {
00044                 RULE_NONE = 0,
00045                 RULE_CMD,
00046                 RULE_CLIENT,
00047         } type;
00048         char *origin;
00049         regex_t *event;
00050         union {
00051                 char *cmd;
00052                 int fd;
00053         } action;
00054         struct rule *next;
00055         struct rule *prev;
00056 };
00057 struct rule_list {
00058         struct rule *head;
00059         struct rule *tail;
00060 };
00061 static struct rule_list cmd_list;
00062 static struct rule_list client_list;
00063 
00064 /* rule routines */
00065 static void enlist_rule(struct rule_list *list, struct rule *r);
00066 static void delist_rule(struct rule_list *list, struct rule *r);
00067 static struct rule *new_rule(void);
00068 static void free_rule(struct rule *r);
00069 
00070 /* other helper routines */
00071 static void lock_rules(void);
00072 static void unlock_rules(void);
00073 static sigset_t *signals_handled(void);
00074 static struct rule *parse_file(const char *file);
00075 static struct rule *parse_client(int client);
00076 static int do_cmd_rule(struct rule *r, const char *event);
00077 static int do_client_rule(struct rule *r, const char *event);
00078 static int safe_write(int fd, const char *buf, int len);
00079 static char *parse_cmd(const char *cmd, const char *event);
00080 static int check_escapes(const char *str);
00081 
00082 /* helper */
00083 static int
00084 path_is_dir(const char *path)
00085 {
00086         struct stat s;
00087 
00088         if (stat(path, &s)) {
00089                 acpid_log("ERR: stat(\"%s\"): %s\n", path, strerror(errno));
00090                 return 0;
00091         }
00092         return S_ISDIR(s.st_mode);
00093 }
00094 
00095 /*
00096  * read in all the configuration files
00097  */
00098 int
00099 acpid_read_conf(const char *confdir)
00100 {
00101         DIR *dir;
00102         struct dirent *dirent;
00103         char *file = NULL;
00104         int nrules = 0;
00105 
00106         lock_rules();
00107 
00108         dir = opendir(confdir);
00109         if (!dir) {
00110                 acpid_log("ERR: opendir(%s): %s\n",
00111                         confdir, strerror(errno));
00112                 unlock_rules();
00113                 return -1;
00114         }
00115 
00116         /* scan all the files */
00117         while ((dirent = readdir(dir))) {
00118                 int len;
00119                 struct rule *r;
00120 
00121                 if (dirent->d_name[0] == '.')
00122                         continue; /* skip dotfiles */
00123 
00124                 len = strlen(dirent->d_name) + strlen(confdir) + 2;
00125                 file = (char *)malloc(len);
00126                 if (!file) {
00127                         acpid_log("ERR: malloc(): %s\n", strerror(errno));
00128                         unlock_rules();
00129                         return -1;
00130                 }
00131                 snprintf(file, len, "%s/%s", confdir, dirent->d_name);
00132 
00133                 if (path_is_dir(file)) {
00134                         free(file);
00135                         continue; /* skip subdirs */
00136                 }
00137 
00138                 r = parse_file(file);
00139                 if (r) {
00140                         enlist_rule(&cmd_list, r);
00141                         nrules++;
00142                 }
00143                 free(file);
00144         }
00145         closedir(dir);
00146         unlock_rules();
00147 
00148         acpid_log("%d rule%s loaded\n", nrules, (nrules == 1)?"":"s");
00149 
00150         return 0;
00151 }
00152 
00153 /*
00154  * cleanup all rules
00155  */
00156 int
00157 acpid_cleanup_rules(void)
00158 {
00159         struct rule *p;
00160         struct rule *next;
00161 
00162         lock_rules();
00163 
00164         if (acpid_debug >= 3) {
00165                 acpid_log("DBG: cleaning up rules\n");
00166         }
00167 
00168         /* tell our clients to buzz off */
00169         p = client_list.head;
00170         while (p) {
00171                 next = p->next;
00172                 delist_rule(&client_list, p);
00173                 close(p->action.fd);
00174                 free_rule(p);
00175                 p = next;
00176         }
00177 
00178         /* clear out our conf rules */
00179         p = cmd_list.head;
00180         while (p) {
00181                 next = p->next;
00182                 delist_rule(&cmd_list, p);
00183                 free_rule(p);
00184                 p = next;
00185         }
00186 
00187         unlock_rules();
00188 
00189         return 0;
00190 }
00191 
00192 static struct rule *
00193 parse_file(const char *file)
00194 {
00195         FILE *fp;
00196         char buf[512];
00197         int line = 0;
00198         struct rule *r;
00199 
00200         if (acpid_debug) {
00201                 acpid_log("DBG: parsing conf file %s\n", file);
00202         }
00203 
00204         fp = fopen(file, "r");
00205         if (!fp) {
00206                 acpid_log("ERR: fopen(%s): %s\n", file, strerror(errno));
00207                 return NULL;
00208         }
00209 
00210         /* make a new rule */
00211         r = new_rule();
00212         if (!r) {
00213                 fclose(fp);
00214                 return NULL;
00215         }
00216         r->type = RULE_CMD;
00217         r->origin = strdup(file);
00218         if (!r->origin) {
00219                 acpid_log("ERR: strdup(): %s\n", strerror(errno));
00220                 free_rule(r);
00221                 fclose(fp);
00222                 return NULL;
00223         }
00224 
00225         /* read each line */
00226         while (!feof(fp)) {
00227                 char *p = buf;
00228                 char key[64];
00229                 char val[512];
00230                 int n;
00231 
00232                 line++;
00233                 memset(key, 0, sizeof(key));
00234                 memset(val, 0, sizeof(val));
00235 
00236                 if (fgets(buf, sizeof(buf)-1, fp) == NULL) {
00237                         continue;
00238                 }
00239 
00240                 /* skip leading whitespace */
00241                 while (*p && isspace((int)*p)) {
00242                         p++;
00243                 }
00244                 /* blank lines and comments get ignored */
00245                 if (!*p || *p == '#') {
00246                         continue;
00247                 }
00248 
00249                 /* quick parse */
00250                 n = sscanf(p, "%63[^=\n]=%255[^\n]", key, val);
00251                 if (n != 2) {
00252                         acpid_log("can't parse %s at line %d\n",
00253                                 file, line);
00254                         continue;
00255                 }
00256                 if (acpid_debug >= 3) {
00257                         acpid_log("DBG:    key=\"%s\" val=\"%s\"\n", key, val);
00258                 }
00259                 /* handle the parsed line */
00260                 if (!strcasecmp(key, "event")) {
00261                         int rv;
00262                         r->event = malloc(sizeof(regex_t));
00263                         if (!r) {
00264                                 acpid_log("ERR: malloc(): %s\n",
00265                                         strerror(errno));
00266                                 free_rule(r);
00267                                 fclose(fp);
00268                                 return NULL;
00269                         }
00270                         rv = regcomp(r->event, val, RULE_REGEX_FLAGS);
00271                         if (rv) {
00272                                 char buf[128];
00273                                 regerror(rv, r->event, buf, sizeof(buf));
00274                                 acpid_log("ERR: regcomp(): %s\n", buf);
00275                                 free_rule(r);
00276                                 fclose(fp);
00277                                 return NULL;
00278                         }
00279                 } else if (!strcasecmp(key, "action")) {
00280                         if (check_escapes(val) < 0) {
00281                                 acpid_log("ERR: can't load file %s\n", file);
00282                                 free_rule(r);
00283                                 fclose(fp);
00284                                 return NULL;
00285                         }
00286                         r->action.cmd = strdup(val);
00287                         if (!r->action.cmd) {
00288                                 acpid_log("ERR: strdup(): %s\n",
00289                                         strerror(errno));
00290                                 free_rule(r);
00291                                 fclose(fp);
00292                                 return NULL;
00293                         }
00294                 } else {
00295                         acpid_log("unknown option '%s' in %s at line %d\n",
00296                                 key, file, line);
00297                         continue;
00298                 }
00299         }
00300         if (!r->event || !r->action.cmd) {
00301                 if (acpid_debug) {
00302                         acpid_log("DBG: skipping incomplete file %s\n", file);
00303                 }
00304                 free_rule(r);
00305                 return NULL;
00306         }
00307         fclose(fp);
00308 
00309         return r;
00310 }
00311 
00312 int
00313 acpid_add_client(int clifd, const char *origin)
00314 {
00315         struct rule *r;
00316         int nrules = 0;
00317 
00318         acpid_log("client connected from %s\n", origin);
00319 
00320         r = parse_client(clifd);
00321         if (r) {
00322                 r->origin = strdup(origin);
00323                 enlist_rule(&client_list, r);
00324                 nrules++;
00325         }
00326 
00327         acpid_log("%d client rule%s loaded\n", nrules, (nrules == 1)?"":"s");
00328 
00329         return 0;
00330 }
00331 
00332 static struct rule *
00333 parse_client(int client)
00334 {
00335         struct rule *r;
00336         int rv;
00337 
00338         /* make a new rule */
00339         r = new_rule();
00340         if (!r) {
00341                 return NULL;
00342         }
00343         r->type = RULE_CLIENT;
00344         r->action.fd = client;
00345         r->event = malloc(sizeof(regex_t));
00346         if (!r) {
00347                 acpid_log("ERR: malloc(): %s\n", strerror(errno));
00348                 free_rule(r);
00349                 return NULL;
00350         }
00351         rv = regcomp(r->event, ".*", RULE_REGEX_FLAGS);
00352         if (rv) {
00353                 char buf[128];
00354                 regerror(rv, r->event, buf, sizeof(buf));
00355                 acpid_log("ERR: regcomp(): %s\n", buf);
00356                 free_rule(r);
00357                 return NULL;
00358         }
00359 
00360         return r;
00361 }
00362 
00363 /*
00364  * a few rule methods
00365  */
00366 
00367 static void
00368 enlist_rule(struct rule_list *list, struct rule *r)
00369 {
00370         r->next = r->prev = NULL;
00371         if (!list->head) {
00372                 list->head = list->tail = r;
00373         } else {
00374                 list->tail->next = r;
00375                 r->prev = list->tail;
00376                 list->tail = r;
00377         }
00378 }
00379 
00380 static void
00381 delist_rule(struct rule_list *list, struct rule *r)
00382 {
00383         if (r->next) {
00384                 r->next->prev = r->prev;
00385         } else {
00386                 list->tail = r->prev;
00387         }
00388 
00389         if (r->prev) {
00390                 r->prev->next = r->next;
00391         } else {
00392                 list->head = r->next;;
00393         }
00394 
00395         r->next = r->prev = NULL;
00396 }
00397 
00398 static struct rule *
00399 new_rule(void)
00400 {
00401         struct rule *r;
00402 
00403         r = malloc(sizeof(*r));
00404         if (!r) {
00405                 acpid_log("ERR: malloc(): %s\n", strerror(errno));
00406                 return NULL;
00407         }
00408 
00409         r->type = RULE_NONE;
00410         r->origin = NULL;
00411         r->event = NULL;
00412         r->action.cmd = NULL;
00413         r->prev = r->next = NULL;
00414 
00415         return r;
00416 }
00417 
00418 /* I hope you delisted the rule before you free() it */
00419 static void
00420 free_rule(struct rule *r)
00421 {
00422         if (r->type == RULE_CMD) {
00423                 if (r->action.cmd) {
00424                         free(r->action.cmd);
00425                 }
00426         }
00427 
00428         if (r->origin) {
00429                 free(r->origin);
00430         }
00431         if (r->event) {
00432                 regfree(r->event);
00433                 free(r->event);
00434         }
00435 
00436         free(r);
00437 }
00438 
00439 /*
00440  * the main hook for propogating events
00441  */
00442 int
00443 acpid_handle_event(const char *event)
00444 {
00445         struct rule *p;
00446         int nrules = 0;
00447         struct rule_list *ar[] = { &client_list, &cmd_list, NULL };
00448         struct rule_list **lp;
00449 
00450         /* make an event be atomic wrt known signals */
00451         lock_rules();
00452 
00453         /* scan each rule list for any rules that care about this event */
00454         for (lp = ar; *lp; lp++) {
00455                 struct rule_list *l = *lp;
00456                 p = l->head;
00457                 while (p) {
00458                         /* the list can change underneath us */
00459                         struct rule *pnext = p->next;
00460                         if (!regexec(p->event, event, 0, NULL, 0)) {
00461                                 /* a match! */
00462                                 if (acpid_debug) {
00463                                         acpid_log("DBG: rule from %s matched\n",
00464                                                 p->origin);
00465                                 }
00466                                 nrules++;
00467                                 if (p->type == RULE_CMD) {
00468                                         do_cmd_rule(p, event);
00469                                 } else if (p->type == RULE_CLIENT) {
00470                                         do_client_rule(p, event);
00471                                 } else {
00472                                         acpid_log("unknown rule type: %d\n",
00473                                                 p->type);
00474                                 }
00475                         } else {
00476                                 if (acpid_debug >= 3) {
00477                                         acpid_log("DBG: rule from %s did not "
00478                                                 "match\n", p->origin);
00479                                 }
00480                         }
00481                         p = pnext;
00482                 }
00483         }
00484 
00485         unlock_rules();
00486 
00487         if (acpid_debug) {
00488                 acpid_log("DBG: %d total rule%s matched\n",
00489                         nrules, (nrules == 1)?"":"s");
00490         }
00491 
00492         return 0;
00493 }
00494 
00495 /* helper functions to block signals while iterating */
00496 static sigset_t *
00497 signals_handled(void)
00498 {
00499         static sigset_t sigset;
00500 
00501         sigemptyset(&sigset);
00502         sigaddset(&sigset, SIGHUP);
00503         sigaddset(&sigset, SIGTERM);
00504         sigaddset(&sigset, SIGQUIT);
00505         sigaddset(&sigset, SIGINT);
00506 
00507         return &sigset;
00508 }
00509 
00510 static void
00511 lock_rules(void)
00512 {
00513         if (acpid_debug >= 4) {
00514                 acpid_log("DBG: blocking signals for rule lock\n");
00515         }
00516         sigprocmask(SIG_BLOCK, signals_handled(), NULL);
00517 }
00518 
00519 static void
00520 unlock_rules(void)
00521 {
00522         if (acpid_debug >= 4) {
00523                 acpid_log("DBG: unblocking signals for rule lock\n");
00524         }
00525         sigprocmask(SIG_UNBLOCK, signals_handled(), NULL);
00526 }
00527 
00528 /*
00529  * the meat of the rules
00530  */
00531 
00532 static int
00533 do_cmd_rule(struct rule *rule, const char *event)
00534 {
00535         pid_t pid;
00536         int status;
00537         const char *action;
00538 
00539         pid = fork();
00540         switch (pid) {
00541         case -1:
00542                 acpid_log("ERR: fork(): %s\n", strerror(errno));
00543                 return -1;
00544         case 0: /* child */
00545                 /* parse the commandline, doing any substitutions needed */
00546                 action = parse_cmd(rule->action.cmd, event);
00547                 acpid_log("executing action \"%s\"\n", action);
00548 
00549                 /* reset signals */
00550                 signal(SIGHUP, SIG_DFL);
00551                 signal(SIGTERM, SIG_DFL);
00552                 signal(SIGINT, SIG_DFL);
00553                 signal(SIGQUIT, SIG_DFL);
00554                 signal(SIGPIPE, SIG_DFL);
00555                 sigprocmask(SIG_UNBLOCK, signals_handled(), NULL);
00556 
00557                 acpid_log("BEGIN HANDLER MESSAGES\n");
00558                 execl("/bin/sh", "/bin/sh", "-c", action, NULL);
00559                 /* should not get here */
00560                 acpid_log("ERR: execl(): %s\n", strerror(errno));
00561                 exit(EXIT_FAILURE);
00562         }
00563 
00564         /* parent */
00565         waitpid(pid, &status, 0);
00566         acpid_log("END HANDLER MESSAGES\n");
00567         if (WIFEXITED(status)) {
00568                 acpid_log("action exited with status %d\n",
00569                         WEXITSTATUS(status));
00570         } else if (WIFSIGNALED(status)) {
00571                 acpid_log("action exited on signal %d\n", WTERMSIG(status));
00572         } else {
00573                 acpid_log("action exited with status %d\n", status);
00574         }
00575 
00576         return 0;
00577 }
00578 
00579 static int
00580 do_client_rule(struct rule *rule, const char *event)
00581 {
00582         int r;
00583         int client = rule->action.fd;
00584 
00585         acpid_log("notifying client %s\n", rule->origin);
00586 
00587         r = safe_write(client, event, strlen(event));
00588         if (r < 0 && errno == EPIPE) {
00589                 /* closed */
00590                 acpid_log("client has disconnected\n");
00591                 delist_rule(&client_list, rule);
00592                 return -1;
00593         }
00594         safe_write(client, "\n", 1);
00595 
00596         return 0;
00597 }
00598 
00599 #define NTRIES 100
00600 static int
00601 safe_write(int fd, const char *buf, int len)
00602 {
00603         int r;
00604         int ttl = 0;
00605         int ntries = NTRIES;
00606 
00607         do {
00608                 r = write(fd, buf+ttl, len-ttl);
00609                 if (r < 0) {
00610                         if (errno != EAGAIN && errno != EINTR) {
00611                                 /* a legit error */
00612                                 return r;
00613                         }
00614                         ntries--;
00615                 } else if (r > 0) {
00616                         /* as long as we make forward progress, reset ntries */
00617                         ntries = NTRIES;
00618                         ttl += r;
00619                 }
00620         } while (ttl < len && ntries);
00621 
00622         if (!ntries) {
00623                 /* crap */
00624                 if (acpid_debug >= 2) {
00625                         acpid_log("uh-oh! safe_write() timed out\n");
00626                 }
00627                 return r;
00628         }
00629 
00630         return ttl;
00631 }
00632 
00633 static char *
00634 parse_cmd(const char *cmd, const char *event)
00635 {
00636         static char buf[4096];
00637         int i;
00638         const char *p;
00639 
00640         p = cmd;
00641         i = 0;
00642 
00643         memset(buf, 0, sizeof(buf));
00644         while (i < (sizeof(buf)-1)) {
00645                 if (*p == '%') {
00646                         p++;
00647                         if (*p == 'e') {
00648                                 /* handle an event expansion */
00649                                 int size = sizeof(buf) - i;
00650                                 size = snprintf(buf+i, size, "%s", event);
00651                                 i += size;
00652                                 p++;
00653                                 continue;
00654                         }
00655                 }
00656                 if (!*p) {
00657                         break;
00658                 }
00659                 buf[i++] = *p++;
00660         }
00661         if (acpid_debug >= 2) {
00662                 acpid_log("DBG: expanded \"%s\" -> \"%s\"\n", cmd, buf);
00663         }
00664 
00665         return buf;
00666 }
00667 
00668 static int
00669 check_escapes(const char *str)
00670 {
00671         const char *p;
00672         int r = 0;
00673 
00674         p = str;
00675         while (*p) {
00676                 /* found an escape */
00677                 if (*p == '%') {
00678                         p++;
00679                         if (!*p) {
00680                                 acpid_log("invalid escape at EOL\n");
00681                                 return -1;
00682                         } else if (*p != '%' && *p != 'e') {
00683                                 acpid_log("invalid escape \"%%%c\"\n", *p);
00684                                 r = -1;
00685                         }
00686                 }
00687                 p++;
00688         }
00689         return r;
00690 }

© sourcejam.com 2005-2008