#define CONFIG_KERNELD #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../Version.h" #include /* * This is kerneld, the user level handler of kernel requests. * Copyright (C) 1995, 1996 Bjorn Ekwall * See the file "COPYING" for your rights. * * * The requests arrive on a specific IPC message queue, * and the type of the message specifies the request. * * The message consists of a header followed by "char text[]". * The parameter for the request is stored in the "text" field. * If the "id" field in the header is non-zero, then the kernel expects * an answer, where the message type of the answer should be "id". * In the return message, the "id" will contain the status of the request. * If the "id" of the request is zero, the kernel does not expect an answer. * * The "text" field can be used to return any generated information. * (See e.g. KERNELD_SYSTEM) * * (C) Bjorn Ekwall in May 1995 * This software is placed under the GPL, the text of which can be found * in the root directory of this release. */ /* * Blocking/unblocking of SIGCHLD due to: Eduardo Blanco * Better error reports by: Lars Fenneberg */ #define MAXCMD 255 /* * The _real_ maximum message type is KERNELD_MAXCMD, * but we reserve some types for other daemons... */ #define MSIZE 1024 /* might be MSGMAX instead... almost 4k nowadays */ #define DELAY_TIME 60 /* adjustable with the "delay=..." parameter */ #define JOB_DONE 0 #define REQUEST_ROUTE "/sbin/request-route" #ifdef DEBUG time_t t0; #define DPRINT(arg) if (debug) { printf("%d:", (int)time(0) - (int)t0);printf arg; } #else #define DPRINT(arg) #endif struct clear_text { int message; char *text; } clear_text[] = { { KERNELD_CANCEL_RELEASE_MODULE, "cancel_release_module" }, { KERNELD_SYSTEM, "system" }, { KERNELD_REQUEST_MODULE, "request_module" }, { KERNELD_DELAYED_RELEASE_MODULE, "delayed_release_module" }, { KERNELD_RELEASE_MODULE, "release_module" }, { KERNELD_REQUEST_ROUTE, "request_route" }, { KERNELD_BLANKER, "screenblanker" }, #ifdef KERNELD_GET_PERSIST { KERNELD_GET_PERSIST, "get_persist" }, { KERNELD_SET_PERSIST, "set_persist" }, #endif { MAXCMD, "debug" }, { 0, 0 } }; #ifdef NO_GDBM typedef struct { char *dptr; int dsize; } datum; #else #include #endif struct persist_t { struct persist_t *next; datum val; datum key; }; struct persist_t *persist_head; char kd_dbmfile[200]; /* or whatever... */ int have_request_route; /* set in gdbm_reload, i.e. also at "kill -1" */ #ifndef NO_GDBM GDBM_FILE dbf; GDBM_FILE kddbmopen(int how) { return dbf = gdbm_open(kd_dbmfile, 0, how, 0600, NULL); } #endif void gdbm_reload(int dummy) { #ifndef NO_GDBM datum key, nextkey, data; struct persist_t *p = persist_head; struct persist_t *x; if (kddbmopen(GDBM_READER)) { /* clean any up old data */ while ((x = p)) { p = p->next; free(x->key.dptr); free(x->val.dptr); free(x); } persist_head = NULL; key = gdbm_firstkey(dbf); while (key.dptr) { data = gdbm_fetch(dbf, key); /* Create entry */ x = (struct persist_t *)malloc(sizeof(*x)); if (x == NULL) /* fail, ignore request */ return; x->key = key; x->val = data; x->next = persist_head; persist_head = x; nextkey = gdbm_nextkey(dbf, key); key = nextkey; } gdbm_close(dbf); } #endif if (access(REQUEST_ROUTE, X_OK) == 0) have_request_route = 1; else have_request_route = 0; } int qid; /* * If "kerneld" is called with the argument "keep", * all requests for module release will be ignored. * * If "delay=10" is used as an argument, the default delay * will be changed from the preset value of 60. */ int keep = 0; int delay = DELAY_TIME; int debug = 0; int want_type = -MAXCMD; int dev_null; struct job { struct job *next; pid_t pid; int status; int reply_size; int reply_fd; struct kerneld_msg msg; }; struct job *job_head = NULL; /* * Handle both old and new kerneld message formats */ struct oldkerneld_msg { long mtype; long id; char text[1]; }; int do_msgsnd(int msqid, struct kerneld_msg *msg, size_t msgsz, int msgflg) { #ifdef NEW_KERNELD_PROTOCOL /* i.e. using extended kerneld protocol */ if (msg->version == 0) { if (msgsz) { struct oldkerneld_msg *omsg = (struct oldkerneld_msg *)msg; memcpy(omsg->text, msg->text, msgsz); } } else msgsz += sizeof(short int) + sizeof(short int); #endif msgsz += sizeof(long); return msgsnd(msqid, (struct msgbuf *)msg, msgsz, msgflg); } int do_msgrcv(int msqid, struct kerneld_msg *msg, size_t msgsz, long msgtyp, int msgflg) { int sz; sz = msgrcv(msqid, (struct msgbuf *)msg, msgsz, msgtyp, msgflg); #ifdef NEW_KERNELD_PROTOCOL /* i.e. using extended kerneld protocol */ if (sz > 0) { if (msg->version != 2) { struct oldkerneld_msg *omsg = (struct oldkerneld_msg *)msg; memcpy(msg->text, omsg->text, sz); msg->pid = msg->version = 0; } } #endif return sz; } void do_putenv(struct job *job) { #ifdef NEW_KERNELD_PROTOCOL /* i.e. using extended kerneld protocol */ char triggerpid[30]; if (job->msg.version != 0) { sprintf(triggerpid, "KERNELD_TRIGGER=%d", job->msg.pid); putenv(triggerpid); } #endif } /* * Get clear text corresponding to message type */ char * type2text(int type) { int chk; for (chk = 0; clear_text[chk].message != 0; ++chk) { if (clear_text[chk].message == type) break; } return (clear_text[chk].text)?(clear_text[chk].text):"unknown"; } /* Generate an error message with the syslog facility. */ void kerneld_error (const char *ctl, ...) { char buf[1000]; va_list list; va_start (list,ctl); vsprintf (buf,ctl,list); syslog (LOG_ERR,"%s",buf); } void kerneld_perror (char *name) { kerneld_error ("error: %s: %s", name, strerror(errno)); exit (1); } void handle_child(int sig); struct sigaction child_action = { handle_child, 0, SA_RESTART | SA_NOMASK, }; void handle_timer(int sig); struct sigaction timer_action = { handle_timer, 0, SA_RESTART | SA_NOMASK, }; struct sigaction hup_action = { gdbm_reload, 0, SA_RESTART | SA_NOMASK, }; volatile int timer = 0; extern int errno; void handle_child(int sig) { struct job *job; int pid; int status; if ((pid = waitpid(-1, &status, WNOHANG)) <= 0) return; for (job = job_head; job; job = job->next) { if (job->pid == pid) { job->pid = JOB_DONE; job->status = WEXITSTATUS(status); /* don't break, more jobs might be waiting... */ DPRINT(("SIGCHLD: job (%08lx), pid=%d, status=%d\n", (long)job, pid, job->status)); } } } void handle_timer(int sig) { timer = 1; alarm(delay); } /* * Execute the requested kerneld task, return pid or -errno. * If pid is set to 0 then no process has been spawned. * * Note that the job has been allocated with calloc, so all * fields are already zero (including the status). */ int spawn_it(struct job *newjob) { struct job *job; #ifdef KERNELD_GET_PERSIST struct persist_t *persist; #endif int pipes[2]; int status; int pid = 0; int op; switch (op = newjob->msg.mtype) { case KERNELD_CANCEL_RELEASE_MODULE: if (keep) break; /* else */ for (job = job_head; job; job = job->next) { if ((job->msg.mtype == KERNELD_DELAYED_RELEASE_MODULE) && (job->pid != JOB_DONE) && (strcmp(job->msg.text, newjob->msg.text) == 0)) { kill(job->pid, SIGINT); DPRINT(("job (%08lx), pid %d terminated\n", (long)job, job->pid)); } } break; case KERNELD_SYSTEM: if (newjob->msg.id) /* reply wanted */ pipe(pipes); if ((pid = fork()) == 0) { if (newjob->msg.id) { /* reply wanted */ close(1); dup(pipes[1]); close(pipes[0]); close(pipes[1]); close(0);close(2); dup(dev_null); dup(dev_null); } else { close(0);close(1);close(2); dup(dev_null); dup(dev_null); dup(dev_null); } do_putenv(newjob); /* signal(SIGCHLD, SIG_DFL); */ status = system(newjob->msg.text); exit(WEXITSTATUS(status)); } if ((pid > 0) && newjob->msg.id) { /* reply wanted */ newjob->reply_fd = pipes[0]; close(pipes[1]); } break; case KERNELD_REQUEST_MODULE: if (!keep) /* * re-arm the timer for auto-removal, * keep an auto-loaded module at least this long */ alarm(delay); if ((pid = fork()) == 0) { close(0);close(1);close(2); dup(dev_null); dup(dev_null); dup(dev_null); do_putenv(newjob); execlp("/sbin/modprobe", "modprobe", "-k", "-s", newjob->msg.text, 0); kerneld_perror("/sbin/modprobe"); sleep(2); exit(1); } break; case KERNELD_DELAYED_RELEASE_MODULE: case KERNELD_RELEASE_MODULE: if (keep) pid = 0; /* else */ if ((pid = fork()) == 0) { close(0);close(1);close(2); dup(dev_null); dup(dev_null); dup(dev_null); if (op == KERNELD_DELAYED_RELEASE_MODULE) sleep(delay); do_putenv(newjob); execlp("/sbin/modprobe", "modprobe", "-r", "-s", newjob->msg.text, 0); kerneld_perror("/sbin/modprobe"); sleep(2); exit(1); } break; case KERNELD_REQUEST_ROUTE: /* set in gdbm_reload, i.e. also at "kill -1" */ if (!have_request_route) break; /* check if there is a previous similar route request running */ for (job = job_head; job; job = job->next) { if ((job->msg.mtype == KERNELD_REQUEST_ROUTE) && job->pid && (job->pid != JOB_DONE) && (strcmp(job->msg.text, newjob->msg.text) == 0)) { /* wait for the same pid */ pid = job->pid; DPRINT(("job (%08lx), depends on job(%08lx)\n", (long)newjob, (long)job)); break; /* for-loop */ } } /* no previous request is running, so start a new one */ if ((pid == 0) && ((pid = fork()) == 0)) { close(0);close(1);close(2); dup(dev_null); dup(dev_null); dup(dev_null); do_putenv(newjob); execlp(REQUEST_ROUTE, "request-route", newjob->msg.text, 0); kerneld_perror(REQUEST_ROUTE); sleep(2); exit(1); } break; case KERNELD_BLANKER: /* Find any previous running blanker */ for (job = job_head; job; job = job->next) { if ((job->msg.mtype == KERNELD_BLANKER) && job->pid && (job->pid != JOB_DONE)) { pid = job->pid; DPRINT(("job (%08lx), depends on job(%08lx)\n", (long)newjob, (long)job)); break; /* for-loop */ } } if (strcmp(newjob->msg.text, "on") == 0) { if ((pid == 0) && ((pid = fork()) == 0)) { /* no previus blanker is running */ close(0);close(1);close(2); dup(dev_null); dup(dev_null); dup(dev_null); do_putenv(newjob); execlp("/sbin/screenblanker","screenblanker",0); kerneld_perror("/sbin/screenblanker"); sleep(2); exit(1); } } else { /* turn the blanker off */ /* Note that pid is already set to the correct value */ /* Let the screen blanker report back instead */ if (pid != 0) { job->msg.id = newjob->msg.id; kill((pid_t)pid, SIGQUIT); } else { /* something has happened to the blanker */ /* fake a "finish_jobs()" */ newjob->msg.mtype = newjob->msg.id; newjob->msg.id = 1; do_msgsnd(qid, &newjob->msg, 0, 0); } pid = 0; newjob->msg.id = 0; } break; case MAXCMD: /* debug entry, used by kdstat */ { char *p = newjob->msg.text; struct msgbuf msgp; int done = 0; int count = 0; if (strcmp(p, "debug") == 0) debug = 1; else if (strcmp(p, "nodebug") == 0) debug = 0; else if (strcmp(p, "keep") == 0) keep = 1; else if (strcmp(p, "nokeep") == 0) keep = 0; else if (strncmp(p, "delay=", 6) == 0) { count = atoi(p + 6); if (count) { delay = count; alarm(delay); } } else if (strcmp(p, "flush") == 0) { count = 0; while (msgrcv(qid, &msgp, 1, 0, IPC_NOWAIT | MSG_NOERROR) >= 0) ++count; sprintf(p, "flushed %d entries\n", count); p += strlen(p); } sprintf(p, "Version " MODULES_VERSION ", pid=%d, delay=%d, %skeep, %sdebug\n", getpid(), delay, keep?"":"no", debug?"":"no"); p += strlen(p); count = 0; for (job = job_head; job; job = job->next) { if (p - newjob->msg.text > MSIZE - 40) { done = 1; break; /* for-loop */ } ++count; if (count == 1) { sprintf(p, "job queue:\n"); p += strlen(p); } sprintf(p, "pid %d, type %s ('%s')" #ifdef NEW_KERNELD_PROTOCOL /* i.e. using extended kerneld protocol */ " from pid %d" #endif "\n", job->pid, type2text((int)job->msg.mtype), job->msg.text #ifdef NEW_KERNELD_PROTOCOL /* i.e. using extended kerneld protocol */ , job->msg.pid #endif ); p += strlen(p); } if (!done && !count) { sprintf(p, "no jobs waiting\n"); p += strlen(p); } } newjob->reply_size = strlen(newjob->msg.text); break; #if 0 case GENERIC_EXAMPLE: /* get the parameter from newjob->msg.text */ /* if it's illegal, set newjob->status to the error number */ /* else if it is an internal job: */ /* do it and copy any result back to newjob->msg.text */ /* update newjob->reply_size with the size (in bytes) */ /* update newjob->status if it shouldn't be 0 */ /* else the job should be spawned: */ /* set up the parameters and fork a new process */ /* set pid to the pid of the new process */ break; #endif #ifdef KERNELD_GET_PERSIST case KERNELD_GET_PERSIST: for (persist = persist_head; persist; persist = persist->next) { if ((strcmp(newjob->msg.text, persist->key.dptr) == 0) && persist->val.dsize) { newjob->reply_size = persist->val.dsize; memcpy(newjob->msg.text, persist->val.dptr, newjob->reply_size); newjob->status = newjob->reply_size; break; } } break; case KERNELD_SET_PERSIST: { struct __persist *k = (struct __persist *)newjob->msg.text; struct persist_t **prev; if (k->keylen == 0) break; for (prev = &persist_head, persist = persist_head; persist; prev = &(persist->next), persist = persist->next) { if (strcmp(k->arr, persist->key.dptr) == 0) break; } if ((persist)) { /* Previously defined */ if (k->arglen == 0) { /* Remove entry */ *prev = persist->next; #ifndef NO_GDBM if (kddbmopen(GDBM_WRITER)) { gdbm_delete(dbf, persist->val); gdbm_close(dbf); } #endif free(persist->key.dptr); free(persist->val.dptr); free(persist); break; } /* else */ if (k->arglen != persist->val.dsize) { char *p; if ((p = malloc(k->arglen)) == NULL) /* fail, but keep the old value */ break; /* else */ free(persist->val.dptr); persist->val.dptr = p; persist->val.dsize = k->arglen; } } else { /* Not previously defined */ if (k->arglen == 0) break; /* Create entry */ persist = (struct persist_t *)malloc(sizeof(*persist)); if (persist == NULL) /* fail, ignore request */ break; if ((persist->key.dptr = malloc(k->keylen)) == NULL) { free(persist); /* fail, ignore request */ break; } if ((persist->val.dptr = malloc(k->arglen)) == NULL) { free(persist->key.dptr); free(persist); /* fail, ignore request */ break; } strcpy(persist->key.dptr, k->arr); persist->key.dsize = k->keylen; persist->val.dsize = k->arglen; persist->next = persist_head; persist_head = persist; } /* Update value */ if (memcmp(persist->val.dptr, k->arr + k->keylen, k->arglen) == 0) break; /* else */ memcpy(persist->val.dptr, k->arr + k->keylen, k->arglen); #ifndef NO_GDBM if (kddbmopen(GDBM_WRCREAT)) { gdbm_store(dbf, persist->key, persist->val, GDBM_REPLACE); gdbm_close(dbf); } #endif } break; #endif default: /* unknown */ kerneld_error ("kerneld: unknown message type: %d", op); newjob->status = 1; /* failed... ? */ break; } return pid; } /* * Look for finished jobs and take care of them. * Return < 0 if fatal error */ int finish_jobs() { struct job *job; struct job **pjob; int status = 0; DPRINT(("finish_jobs\n")); for (pjob = &job_head, job = job_head; job && (status >= 0); job = *pjob) { if (job->pid == JOB_DONE) { /* * Does the kernel expect an answer? */ if (job->msg.id) { if (job->reply_fd) { struct timeval timeout = { 0, 0 }; fd_set readfds; FD_ZERO(&readfds); FD_SET(job->reply_fd, &readfds); if ((select(job->reply_fd + 1, &readfds, (fd_set *)NULL, (fd_set *)NULL, &timeout) > 0) && FD_ISSET(job->reply_fd, &readfds)) { job->reply_size = read(job->reply_fd, job->msg.text, MSIZE); } close(job->reply_fd); } job->msg.mtype = job->msg.id; job->msg.id = job->status; status = do_msgsnd(qid, &job->msg, ((job->reply_size > 0) ? job->reply_size : 0) , 0); } /* * unlink the job */ DPRINT(("job (%08lx) unlinked\n", (long)job)); *pjob = job->next; free(job); } else /* job not done */ pjob = &(job->next); } return status; } void do_timer() { extern int syscall(int, ...); timer = 0; if (!keep) { /* Thanks Jacques!!! */ struct job *job; for (job = job_head; job; job = job->next) if (job->msg.mtype == KERNELD_REQUEST_MODULE) break; if (job == NULL) syscall( __NR_delete_module, NULL); } } int main(int argc, char *argv[]) { struct job *job; int pid; int status; sigset_t signalset; struct utsname uts_info; /* make me a daemon */ if ((pid = fork()) != 0) { printf("Starting kerneld, version " MODULES_VERSION " (pid %d)\n", pid); exit(0); } openlog ("kerneld",0,LOG_DAEMON); if ((qid = msgget(IPC_PRIVATE, S_IRWXU | IPC_CREAT | IPC_KERNELD)) < 0){ kerneld_error("Can't initialise message queue: %s",strerror(errno)); exit(1); } #ifdef DEBUG t0 = time(0); #endif setbuf(stdout, NULL); syslog (LOG_INFO,"started, pid=%d, qid=%d", getpid(), qid); uname(&uts_info); sprintf(kd_dbmfile, "/lib/modules/%s/persist.gdbm", uts_info.release); gdbm_reload(0); /* dummy arg */ setsid(); if (sigaction(SIGCHLD, &child_action, NULL) < 0) { kerneld_perror("sigaction(SIGCHLD)"); } if (sigaction(SIGALRM, &timer_action, NULL) < 0) { kerneld_perror("sigaction(SIGARLM)"); } if (sigaction(SIGHUP, &hup_action, NULL) < 0) { kerneld_perror("sigaction(SIGHUP)"); } while (argc > 1) { if (strcmp(argv[1], "keep") == 0) keep = 1; else if (strncmp(argv[1], "delay=", 6) == 0) delay = atoi(&(argv[1][6])); else if (strcmp(argv[1], "debug") == 0) debug = 1; else if (strncmp(argv[1], "type=", 5) == 0) want_type = atoi(&(argv[1][5])); ++argv; --argc; } dev_null = open("/dev/null", O_RDWR); if (!keep) alarm(delay); /* start the timer task */ for (;;) { job = (struct job *)calloc(1, sizeof(struct job) + MSIZE); if (job == NULL) { kerneld_perror("calloc(job)"); } restart: while ((status = do_msgrcv(qid, &job->msg, MSIZE, want_type, MSG_NOERROR)) == 0) /*keep on*/; if (status < 0) { /* was the msgrcv interrupted by a signal? */ if (errno == EINTR) { if (timer) { do_timer(); } if (finish_jobs() < 0) { break; /* fatal error */ } /* else */ goto restart; /* wait for message */ } /* else */ break; /* some other error */ } /* else, we got a message */ /* null-terminate the message */ status = (status >= MSIZE) ? (MSIZE - 1) : status; ((struct msgbuf *)&job->msg)->mtext[status] = '\0'; DPRINT(("job (%08lx) scheduled, type %s ('%s')\n", (long)job, type2text((int)job->msg.mtype), job->msg.text)); /* Don't trap SIGCHLD til we add the job to the queue */ sigemptyset(&signalset); sigaddset(&signalset, SIGCHLD); sigaddset(&signalset, SIGHUP); sigprocmask(SIG_BLOCK, &signalset, NULL); if ((pid = spawn_it(job)) < 0) break; /* fatal error */ /* * Link this job into the job queue if something was * spawned or if someone is waiting for an answer. */ if ((pid > 0) || (job->msg.id != 0)) { job->pid = pid; job->next = job_head; job_head = job; DPRINT(("job (%08lx) stored, pid=%d\n", (long)job,pid)); /* take care of all tasks, including non-spawned ones */ if (finish_jobs() < 0) break; /* fatal error */ } else { /* all work requested has been performed */ DPRINT(("job (%08lx) released\n", (long)job)); free(job); } sigprocmask(SIG_UNBLOCK, &signalset, NULL); } kerneld_perror("exit"); return 1; }