From: Neil F. Brown Date: Tue, 6 Jun 2006 09:26:41 +0000 (+1000) Subject: Initial checking X-Git-Url: http://git.neil.brown.name/?a=commitdiff_plain;h=ad77053c4c8dcfe14c33ed2ebc7842c294cbbb32;p=metad.git Initial checking --- ad77053c4c8dcfe14c33ed2ebc7842c294cbbb32 diff --git a/Buildfile b/Buildfile new file mode 100644 index 0000000..f5d5ccd --- /dev/null +++ b/Buildfile @@ -0,0 +1,55 @@ + +all Involves metad meta + +metad Cload metad.o mainloop.o classes.o commands.o control.o daemon.o stream.o read_config.o service.o version.o skip.o strlistdup.o error.o strsplit.o strccmp.o broadcast.o ports.o sendlist.o loadstate.o recvlist.o StrDup + +meta Cload meta.o sendcmd.o broadcast.o dlink.o args.o version.o skip.o ports.o recvlist.o prtime.o StrDup + +StrDup Involves/ultrix strdup.o +StrDup Involves/!ultrix + +#AutoBuilder +args.o C args.c +args.c Includes args.h +broadcast.o C broadcast.c +classes.o C classes.c +classes.c Includes metad.h +commands.o C commands.c +commands.c Includes metad.h skip.h +control.o C control.c +control.c Includes metad.h +daemon.o C daemon.c +daemon.c Includes metad.h +dlink.o C dlink.c +dlink.c Includes dlink.h +error.o C error.c +error.c Includes metad.h +loadstate.o C loadstate.c +loadstate.c Includes metad.h skip.h +mainloop.o C mainloop.c +mainloop.c Includes metad.h skip.h +meta.o C meta.c +meta.c Includes metad.h args.h dlink.h +metad.o C metad.c +metad.c Includes metad.h +nargs.o C nargs.c +nargs.c Includes args.h +ports.o C ports.c +prtime.o C prtime.c +read_config.o C read_config.c +read_config.c Includes metad.h +recvlist.o C recvlist.c +sendcmd.o C sendcmd.c +sendcmd.c Includes skip.h metad.h +sendlist.o C sendlist.c +sendlist.c Includes metad.h skip.h +service.o C service.c +service.c Includes metad.h skip.h +skip.o C skip.c +strccmp.o C strccmp.c +strdup.o C strdup.c +stream.o C stream.c +stream.c Includes metad.h +strlistdup.o C strlistdup.c +strsplit.o C strsplit.c +version.o C version.c diff --git a/PROTOCOL b/PROTOCOL new file mode 100644 index 0000000..2977d95 --- /dev/null +++ b/PROTOCOL @@ -0,0 +1,27 @@ +list +list +version +broad +die +disable +enable +kick [] +run [] +kill +kill +reread +# need leading number for backwardsness + +config file - parsed by strsplit + + service type options /path/of/prog args + +options may be independant + max= crash= dir= user= enabled= +or type dependant + daemon: + min= period= + stream: + port= + listen: + port= \ No newline at end of file diff --git a/TODO b/TODO new file mode 100644 index 0000000..b9ead0d --- /dev/null +++ b/TODO @@ -0,0 +1,7 @@ + +Be careful not to miss a child exit (it has happened) - use non-block wait +log stuff. +when=time for daemon + +optionally connect pipe to stdout/stderr and wait until pipe closed. +optionally have pidfile from which pid may be read if child exits but pipe still open \ No newline at end of file diff --git a/args.c b/args.c new file mode 100644 index 0000000..26bded0 --- /dev/null +++ b/args.c @@ -0,0 +1,119 @@ + +/* + * args - a getopt link argument processor + * + * void *handle = args_init(argc, argv, "argctl"); + * + * args_next(handle, &pos, &inv, &optarg) -> BAD_FLAG/END_ARGS/NO_FLAG/opt + * + * argctl = {}* + * typechar == ? -f or -~f + * + -f arg or -~f + * - -f or -~f arg + * : -f arg only + * + * + */ + +#define IN_ARGS +#include "args.h" +void *malloc(int); +struct handle +{ + int argc; + char **argv; + char *ctl; + int nextarg, nextchar; +}; +#define NULLH ((struct handle *)0) + +struct handle *args_init(int argc, char **argv, char *ctl) +{ + /* if ctl is bad, return NULL, else a handle */ + int i; + struct handle *h; + for (i=0 ; ctl[i]; i+= 2) + if (ctl[i+1] != '?' && ctl[i+1] != '-' && ctl[i+1] != '+' && ctl[i+1] != ':') + break; + if (ctl[i]) + return NULLH; + + h = (struct handle*)malloc(sizeof(struct handle)); + if (h == NULLH) + return h; + h->argc = argc; + h->argv = argv; + h->ctl = ctl; + h->nextarg = 1; + h->nextchar = 0; + return h; +} + +int args_next(struct handle *h, int *pos, int *inv, char **opt) +{ + int invc = 0; + int invfound = 0; + int i; + char *a; + if (h->nextarg >= h->argc) + return END_ARGS; + if (h->nextchar == 0) + { + if (h->argv[h->nextarg][0] != '-') + { + if (pos) *pos = h->nextarg; + if (inv) *inv = 0; + if (opt) *opt = h->argv[h->nextarg]; + h->nextarg++; + return NO_FLAG; + } + h->nextchar++; + } + a = h->argv[h->nextarg]; + while (a[h->nextchar] == '~') + { + invc = !invc; + invfound = 1; + h->nextchar++; + } + for (i=0 ; h->ctl[i] ; i+=2) + if (h->ctl[i] == a[h->nextchar]) + break; + if (h->ctl[i] == 0) + { + h->nextchar = 0; + h->nextarg ++; + return BAD_FLAG; + } + h->nextchar++; + if (pos) *pos = h->nextarg; + if (inv) *inv = invc; + if (a[h->nextchar] == 0) + { + h->nextchar = 0; + h->nextarg ++; + } + switch(h->ctl[i+1]) + { + case '?': + return h->ctl[i]; + case '+': + if (inv) + return h->ctl[i]; + break; + case '-': + if (!inv) + return h->ctl[i]; + break; + case ':': + break; + } + /* need optarg */ + if (h->nextarg >= h->argc) + return BAD_FLAG; + if (opt) + *opt = h->argv[h->nextarg] + h->nextchar; + h->nextarg++; + h->nextchar=0; + return h->ctl[i]; +} diff --git a/args.h b/args.h new file mode 100644 index 0000000..6c9af00 --- /dev/null +++ b/args.h @@ -0,0 +1,9 @@ + +#ifndef IN_ARGS +void *args_init(int argc, char **argv, char *ctl); +int args_next(void *handle, int *pos, int *inv, char **opt); +#endif + +#define END_ARGS 0 +#define BAD_FLAG (-1) +#define NO_FLAG (-2) diff --git a/broadcast.c b/broadcast.c new file mode 100644 index 0000000..b1ffeec --- /dev/null +++ b/broadcast.c @@ -0,0 +1,84 @@ + +/* broadcast a string on all interfaces, to udp_port(); */ + +#include +#include +#include +#include +#ifdef SOLARIS +#include +#endif +#include +#include +#include +#include +#include +#include + +int udp_port(void); + +static struct ifconf iflist; +static int interfaces; +static int sock; +static char *packet; +static struct servent *sv; + +static int ifconfinit() +{ + + static char buf[2048]; + + iflist.ifc_len = sizeof(buf); + iflist.ifc_buf = buf; +/* printf("ifc_len = %d\n", iflist.ifc_len); /**/ + if (ioctl(sock, SIOCGIFCONF, (char *)&iflist)< 0) return -1; +/* printf("ifc_len = %d\n", iflist.ifc_len); /**/ + interfaces = iflist.ifc_len / sizeof(struct ifreq); +/* printf("interfaces = %d\n",interfaces); /**/ +/* for (in=0; inclass)==0) + return classes[c]; + return NULL; +} diff --git a/commands.c b/commands.c new file mode 100644 index 0000000..e8d93a5 --- /dev/null +++ b/commands.c @@ -0,0 +1,253 @@ + +#include "metad.h" +#include "skip.h" + +/* the commands are handled here */ + +static char *gather_arg(char **args) +{ + char *line; + int len = 0; + int a; + for (a=0 ; args[a] ; a++) len += strlen(args[a])+1; + line = (char*)malloc(len+1); + len = 0; + for (a=0 ; args[a] ; a++) + { + strcpy(line+len, args[a]); + len += strlen(args[a]); + line[len++] = ' '; + } + if (len>0) + line[len-1] = '\0'; + else + line[len] = 0; + return line; +} + +static void do_version(char **args, char *host, void *con) +{ + char *rv; + + if (con) + { + extern char version[]; + rv = (char*)malloc(strlen(version)+2); + strcpy(rv+1, version); + rv[0] = 2; + set_reply(con, rv, strlen(version)+2); + } + +} + + +static void do_broad(char **args, char *host, void *con) +{ + /* remaining args are packaged up and broadcast on all interfaces */ + char *line; + line = gather_arg(args+1); + log(LOG_INFO, "Broadcast request from %s for %s", host, line); + broadcast(line); + free(line); +} + +static void do_die(char **args, char *host, void *con) +{ + log(LOG_WARNING, "Die request from %s", host); + exit(1); /* FIXME */ +} + +static void do_disable(char **args, char *host, void *con) +{ + service_t sv; + if (args[1] == NULL) + return_error(con, "No service given for disabling"); + else if ((sv=find_service(args[1]))==NULL) + return_error(con, "Cannot find service %s to disable it", args[1]); + else for ( ; sv ; sv=find_service(NULL)) + if (sv->enabled) + { + log(LOG_INFO, "Disable request from %s for %s", host, sv->service); + (sv->class->disable_service)(sv); + sv->enabled = 0; + } +} + +static void do_enable(char **args, char *host, void *con) +{ + service_t sv; + if (args[1] == NULL) + return_error(con, "No service given for enabling"); + else if ((sv=find_service(args[1]))==NULL) + return_error(con, "Cannot find service %s to enable it", args[1]); + else for ( ; sv ; sv=find_service(NULL)) + if (!sv->enabled) + { + log(LOG_INFO, "Enable request from %s for %s", host, sv->service); + (sv->class->register_service)(sv); + sv->enabled = 1; + } +} + + +static void do_run(char **args, char *host, void *con) +{ + /* attempt to run service called args[1] with METAD_ARG set to remaining args and METAD_HOST set to host + */ + service_t sv; + if (args[1] == NULL) + return_error(con, "No service given to run"); + else if ((sv=find_service(args[1]))==NULL) + return_error(con, "Cannot find service %s to run", args[1]); + else + { + char *env[3]; + char *arg = gather_arg(args+2); + env[0] = strcat(strcpy((char*)malloc(20+strlen(host)), "METAD_REASON=run:"), host); + env[1] = strcat(strcpy((char*)malloc(11+strlen(arg)), "METAD_ARG="), arg); + env[2] = NULL; + for ( ; sv ; sv = find_service(NULL)) + { + /* first clear all hold-times */ + proc_t *pp; + for (pp=skip_first(sv->proc_list) ; pp ; pp=skip_next(pp)) + if ((*pp)->hold_time != 0) + (*pp)->hold_time = 1; + sv->next_hold = 2; + log(LOG_INFO,"starting %s for %s : arg = %s", sv->service, host, arg); + new_proc(sv, env); + } + free(arg); + free(env[1]); + free(env[0]); + } +} + +static void do_kill(char **args, char *host, void *con) +{ + /* args[1] = signal + * args[2] = pid or service name + */ + int sig; + pid_t pid; + service_t sv; + if (args[1] == NULL) + return_error(con, "No signal number given for kill"); + else if ((sig=atoi(args[1]))<= 0) + return_error(con, "Bad signal number given for kill: %s", args[1]); + else if (args[2] == NULL) + return_error(con, "No process id or service name given for kill"); + else if ((pid = atoi(args[2]))>0) + { + proc_t *pp = skip_search(allprocs, &pid); + if (pp) + { + log(LOG_INFO, "killing %s for %s", args[2], host); + if ((*pp)->exit_time == 0) + kill((*pp)->pid, sig); + else if ((*pp)->it_forked > 1) + kill((*pp)->it_forked, sig); + } + else + return_error(con, "Cannot find process %s to kill", args[2]); + } + else if ((sv = find_service(args[2]))!= NULL) + { + for ( ; sv ; sv = find_service(NULL)) + { + proc_t *pp; + for (pp = skip_first(sv->proc_list) ; pp ; pp = skip_next(pp)) + if ((*pp)->exit_time == 0 || (*pp)->it_forked) + { + log(LOG_INFO, + "signalling %s:%d with %d for %s", sv->service, + (*pp)->exit_time?(*pp)->it_forked:(*pp)->pid, + sig, host); + if ((*pp)->exit_time == 0) + kill((*pp)->pid, sig); + else if ((*pp)->it_forked > 1) + kill((*pp)->it_forked, sig); + } + } + } + else + return_error(con, "Cannot find service %s to kill", args[2]); +} + + +static void do_reread(char **args, char *host, void *con) +{ + char *errs = NULL; + int old; + log(LOG_INFO, "Rereading config file for %s", host); + old= errors_to(ERROR_STRING, &errs); + if (read_config(services, NULL) != 0) + return_error(con, "%s", errs); + errors_to(old, NULL); + if (errs) free(errs); +} + +static void do_list(char **args, char *host, void *con) +{ + service_t sv, *svp; + init_return(); + send_byte(3); /* listing */ + if (args[1] == NULL) + { + for (svp = skip_first(services) ; svp ; svp = skip_next(svp)) + { + send_service(*svp); + } + send_byte(0); /* finished */ + do_send(con); + } + else if ((sv=find_service(args[1])) != NULL) + { + for ( ; sv ; sv = find_service(NULL)) + send_service(sv); + send_byte(0); + do_send(con); + } + else + return_error(con, "Cannot find service %s to list", args[1]); +} + +void do_restart(char **args, char *host, void *con) +{ + log(LOG_INFO, "About to restart for %s", host); + control_close(); + prepare_restart(); + restart(); + exit(0); +} + + +static struct commands cmds[] = { + { "list", do_list }, + { "version",do_version }, + { "broad", do_broad }, + { "die", do_die }, + { "disable",do_disable}, + { "enable", do_enable}, + { "kick", do_run}, + { "run", do_run}, + { "kill", do_kill}, + { "reread", do_reread}, + { "restart", do_restart}, + { NULL, NULL} + }; + +int do_command(char **args, char *host, void *con) +{ + int cmd = 0; + for (cmd = 0; cmds[cmd].name ; cmd++) + if (strccmp(cmds[cmd].name, args[0])==0) + break; + if (cmds[cmd].name) + { + (cmds[cmd].proc)(args, host, con); + return 1; + } + else + return 0; +} diff --git a/control.c b/control.c new file mode 100644 index 0000000..1d62968 --- /dev/null +++ b/control.c @@ -0,0 +1,312 @@ + +#define IN_CONTROL +#include +#include "metad.h" +#include +#include +#include +#ifdef SOLARIS +#include +#else +#include +#endif + +#include +#include + +/* control.c - handle the control ports + * listen on udp port for packets with command to obey + * listen on tcp port for connections. + * when get connection, listen for command, and possibly return data + * + */ + +/* only allow one active tcp connection for now, + * but make sure it times out + */ + +static int udp_sock; +static int tcp_listen; + +static struct tcpcon { + int sock; + char buf[1024]; /*for incoming command */ + char host[1024]; /* host connection is from */ + int buflen; /* how much has been read */ + char *outbuf; /* outgoing data */ + int outlen; /* size of outgoing data */ + int outpos; /* how much sent so far */ + time_t connect_time;/* when the connection was established */ +} tcpcon; + + + + +void return_error(struct tcpcon *con, char *fmt, char *a, char *b, char *c) +{ + char buf[1024]; + char *rv; + extern char version[]; + if (con) + { + sprintf(buf, fmt, a, b, c); + sprintf(buf+strlen(buf), " (metad version %s)", version); + rv = (char*)malloc(strlen(buf)+2); + strcpy(rv+1, buf); + rv[0]= 1; + con->outbuf = rv; + con->outlen = strlen(buf)+2; + con->outpos = 0; + } +} + +static int address_ok(struct sockaddr_in *sa, char *host) +{ + struct hostent *he; + int len; + int a; + static char *tail = ".cse.unsw.edu.au"; + + if (ntohs(sa->sin_port) >= 1024 && geteuid() == 0) + return 0; + if (sa->sin_addr.s_addr == htonl(0x7f000001)) + { + strcpy(host, "localhost"); + return 1; /* localhost */ + } + he = gethostbyaddr((char*)&sa->sin_addr, 4, AF_INET); + if (he == NULL) + return 0; + strcpy(host, he->h_name); + he = gethostbyname(host); + if (he == NULL) + return 0; + for (a=0; he->h_addr_list[a] ; a++) + if (memcmp(&sa->sin_addr, he->h_addr_list[a], 4)==0) + { + /* well, we have a believeable name */ + + len = strlen(host); + if (len > strlen(tail) && strccmp(tail, host+len - strlen(tail))== 0) + return 1; + return 0; + } + return 0; +} + +static void run_command(char *buf, char *host, struct tcpcon *con) +{ + char *cp; + char **words, **wp; + + for (cp= buf; *cp ; cp++) + { + if (*cp == '\r' || *cp == '\n') *cp = 0; + } + wp = words = strsplit(buf, " "); + if (isdigit(wp[0][0])) + wp++; /* old gossip put a port number at the start for return info */ + if (!do_command(wp, host, con)) + { + /* possibly return error */ + if (con) + return_error(con, "unknown command %s", wp[0], NULL, NULL); + } +} + +void nodelay(int socket) +{ + int f; + f = fcntl(socket, F_GETFL, 0); + fcntl(socket, F_SETFL, f|O_NDELAY); + fcntl(socket, F_SETFD, 1); /* set close-on-exec */ +} + +int control_init() +{ + udp_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (udp_sock >= 0) + { + struct sockaddr_in sa; + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = udp_port(); + nodelay(udp_sock); + if (bind(udp_sock, (struct sockaddr *)&sa, sizeof(sa)) != 0) + { + error("cannot bind udp port"); + return -1; + } + } + else + { + error("cannot create udp socket"); + return -1; + } + tcp_listen = socket(AF_INET, SOCK_STREAM, 0); + if (tcp_listen >= 0) + { + struct sockaddr_in sa; + int i = 1; + nodelay(tcp_listen); + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = tcp_port(); + setsockopt(tcp_listen, SOL_SOCKET, SO_REUSEADDR, (char*)&i, 4); + if (bind(tcp_listen, (struct sockaddr *)&sa, sizeof(sa)) != 0) + { + error("cannot bind tcp port"); + return -1; + } + listen(tcp_listen, 5); + } + else + { + error("Cannot create tcp socket"); + return -1; + } + tcpcon.sock = -1; + return 0; +} + +void control_close(void) +{ + close(tcp_listen); + close(udp_sock); + close(tcpcon.sock); +} + +void check_control(void) +{ + /* first check udp */ + if (readyon(udp_sock)) + { + char buf[1024]; + char host[1024]; + int n; + struct sockaddr_in sa; + int salen = sizeof(sa); + n = recvfrom(udp_sock, buf, sizeof(buf)-1, 0, (struct sockaddr *)&sa, &salen ); + if (n>0 && address_ok(&sa, host)) + { + buf[n] = 0; + run_command(buf, host, NULL); + } + } + listenon(udp_sock); + + /* then check tcpcon or tcp_listen */ + if (tcpcon.sock != -1) + { + time_t now; + time(&now); + if (tcpcon.connect_time + 120 < now) + { + /* just give up */ + close(tcpcon.sock); + tcpcon.sock = -1; + if (tcpcon.outbuf) free(tcpcon.outbuf); + tcpcon.outbuf = NULL; + listenon(tcp_listen); + } + else if (tcpcon.outbuf) + { + if (canwrite(tcpcon.sock) && tcpcon.outpos < tcpcon.outlen) + { + int l = tcpcon.outlen - tcpcon.outpos; + if (l>1024) l = 1024; + l = write(tcpcon.sock, tcpcon.outbuf+tcpcon.outpos, l); + if (l< 0) + { + close(tcpcon.sock); tcpcon.sock = -1; free(tcpcon.outbuf); + tcpcon.outbuf = NULL; + } + else + tcpcon.outpos += l; + if (tcpcon.outpos >= tcpcon.outlen) + { + close(tcpcon.sock); tcpcon.sock = -1; free(tcpcon.outbuf); + tcpcon.outbuf = NULL; + } + } + if (tcpcon.sock == -1) + listenon(tcp_listen); + else + writeon(tcpcon.sock); + } + else /* we are still reading a command */ + { + if (readyon(tcpcon.sock)) + { + int l = sizeof(tcpcon.buf) - tcpcon.buflen; + l = read(tcpcon.sock, tcpcon.buf+tcpcon.buflen, l-1); + if (l<0) + { + close(tcpcon.sock); tcpcon.sock = -1; + } + else + { + tcpcon.buf[l] = 0; + if (l == 0 || strchr(tcpcon.buf, '\n') || strchr(tcpcon.buf, '\r') || strlen(tcpcon.buf) < l) + { + run_command(tcpcon.buf, tcpcon.host, &tcpcon); + if (tcpcon.outbuf == NULL) + { + tcpcon.outbuf = malloc(1); + tcpcon.outbuf[0] = 0; + tcpcon.outlen = 1; + tcpcon.outpos = 0; + } + } + } + } + if (tcpcon.sock == -1) + listenon(tcp_listen); + else if (tcpcon.outbuf) + writeon(tcpcon.sock); + else + listenon(tcpcon.sock); + + } + } + else + { + if (readyon(tcp_listen)) + { + struct sockaddr_in sa; + int salen = sizeof(sa); + tcpcon.buflen = 0; + tcpcon.outbuf = NULL; + tcpcon.sock = accept(tcp_listen, (struct sockaddr *)&sa, &salen); + if (tcpcon.sock >= 0) + { + nodelay(tcpcon.sock); + if (address_ok(&sa, tcpcon.host)) + { + time(&tcpcon.connect_time); + listenon(tcpcon.sock); + waituntil(tcpcon.connect_time+122); + } + else + { + close(tcpcon.sock); + tcpcon.sock = -1; + } + } + } + if (tcpcon.sock < 0) + listenon(tcp_listen); + } + +} + +void set_reply(struct tcpcon *con, char *reply, int len) +{ + if (con) + { + con->outbuf = reply; + con->outlen = len; + con->outpos = 0; + } + else free(reply); +} diff --git a/daemon.c b/daemon.c new file mode 100644 index 0000000..88c4592 --- /dev/null +++ b/daemon.c @@ -0,0 +1,137 @@ + +#include "metad.h" + +typedef struct daemon_opts +{ + int min; + int period; + time_t last_start; +} *daemon_t; + +#define c(sv) ((daemon_t)((sv)->classinfo)) + +static int daemon_opt(service_t sv, char *opt) +{ + /* understand min= period= */ + if (strncmp(opt, "min=", 4)==0) + { + c(sv)->min = atoi(opt+4); + return 1; + } + if (strncmp(opt, "period=", 7) == 0) + { + char *cp = opt+7; + int num = atoi(cp); + if (num==0) num=1; + while (isdigit(*cp)) cp++; + switch(*cp) { + case 0: break; + case 's': break; + case 'm': num *= 60; break; + case 'h': num *= 3600 ; break; + case 'd': num *= 24*3600 ; break; + default: error("bad period specifier, %s", opt); break; + } + c(sv)->period = num; + return 1; + } + return 0; +} + + +static void daemon_register(service_t sv) +{ + /* nothing to do.. */ + c(sv)->last_start = 0; +} + +static void daemon_unregister(service_t sv) +{ + /* nothing to do... */ + +} + +static int daemon_prefork(service_t sv) +{ + return 0; +} + +static void daemon_check(service_t sv) +{ + /* make sure min are running, and watch for next period */ + char *env[3]; + env[0] = "METAD_REASON=min"; + env[1] = "METAD_ARG="; + env[2] = NULL; + while (c(sv)->min > 0 && count_procs(sv) < c(sv)->min) + { + if (new_proc(sv, env)<=0) + break; + } + if (c(sv)->period > 0 && + c(sv)->last_start + c(sv)->period <= time(0)) + { + env[0] = "METAD_REASON=period"; + new_proc(sv, env); + c(sv)->last_start = time(0); /* even if it didn't start, we tried */ + } + if (c(sv)->period > 0) + waituntil(c(sv)->last_start + c(sv)->period); +} + +static void daemon_copy(service_t from, service_t to) +{ + /* copy the classinfo - min and period */ + daemon_t n,o; + if (from) + { + /* no special state to copy + * the new service should have parsed its own args + * - simonb 11nov2003 + */ + //o = from->classinfo; + //n->min = o->min; + //n->period = o->period; + //n->last_start = o->last_start; + } + else + { + n = (daemon_t)malloc(sizeof(struct daemon_opts)); + n->min = 0; + n->period = 0; + n->last_start = 0; + to->classinfo = n; + } +} + +static void daemon_freestate(service_t sv) +{ + free(sv->classinfo); +} + + +static void daemon_newparent(service_t sv, proc_t p) +{ + c(sv)->last_start = time(0); +} + +static void daemon_newchild(service_t sv) +{ + +} + +static void daemon_send(service_t sv) +{ + /* send min, period, last_start */ + send_byte(1); /* daemon */ + send_int(c(sv)->min); + send_int(c(sv)->period); + send_int(c(sv)->last_start); +} + +struct class daemon_class = + { "daemon", daemon_opt, daemon_register, daemon_check, + daemon_copy, daemon_freestate, daemon_send, + daemon_unregister, daemon_newparent, daemon_newchild, + daemon_prefork + }; diff --git a/dlink.c b/dlink.c new file mode 100644 index 0000000..77c7b5e --- /dev/null +++ b/dlink.c @@ -0,0 +1,74 @@ + +/* doubly linked lists */ + +#include +#include +#include +#include "dlink.h" + + +void *dl_head() +{ + void *h; + h = dl_alloc(0); + dl_next(h) = h; + dl_prev(h) = h; + return h; +} + +void dl_free(v) +void *v; +{ + struct __dl_head *vv = v; + free(vv-1); +} + + +void dl_insert(head, val) +void *head, *val; +{ + dl_next(val) = dl_next(head); + dl_prev(val) = head; + dl_next(dl_prev(val)) = val; + dl_prev(dl_next(val)) = val; +} + +void dl_add(head, val) +void *head, *val; +{ + dl_prev(val) = dl_prev(head); + dl_next(val) = head; + dl_next(dl_prev(val)) = val; + dl_prev(dl_next(val)) = val; +} + +void dl_del(val) +void *val; +{ + if (dl_prev(val) == 0 || dl_next(val) == 0) + return; + dl_prev(dl_next(val)) = dl_prev(val); + dl_next(dl_prev(val)) = dl_next(val); + dl_prev(val) = dl_next(val) = 0; +} + +char *dl_strndup(char *s, int l) +{ + char *n; + if (s == NULL) + return NULL; + n = dl_newv(char, l+1); + if (n == NULL) + return NULL; + else + { + strncpy(n, s, l); + n[l] = 0; + return n; + } +} + +char *dl_strdup(char *s) +{ + return dl_strndup(s, (int)strlen(s)); +} diff --git a/dlink.h b/dlink.h new file mode 100644 index 0000000..996b0d8 --- /dev/null +++ b/dlink.h @@ -0,0 +1,23 @@ + +/* doubley linked lists */ + +struct __dl_head +{ + struct __dl_head * dh_prev; + struct __dl_head * dh_next; +}; + +#define dl_alloc(size) ((void*)(((char*)calloc(1,(size)+sizeof(struct __dl_head)))+sizeof(struct __dl_head))) +#define dl_new(t) ((t*)dl_alloc(sizeof(t))) +#define dl_newv(t,n) ((t*)dl_alloc(sizeof(t)*n)) + +#define dl_next(p) *((void**)&(((struct __dl_head*)(p))[-1].dh_next)) +#define dl_prev(p) *((void**)&(((struct __dl_head*)(p))[-1].dh_prev)) + +void *dl_head(); +char *dl_strdup(char *); +char *dl_strndup(char *, int); +void dl_insert(void*, void*); +void dl_add(void*, void*); +void dl_del(void*); +void dl_free(void*); diff --git a/error.c b/error.c new file mode 100644 index 0000000..43262bb --- /dev/null +++ b/error.c @@ -0,0 +1,112 @@ + +#define IN_ERROR +#include "metad.h" +#include +#include +#define STDARGS +/* following for irix6 !! */ +#define _VA_FP_SAVE_AREA 0 +static int error_dest = ERROR_STDERR; +static char **error_str = NULL; +int err_str_len; + +int errors_to(int where, char **place) +{ + int rv = error_dest; + error_dest = where; + if (where == ERROR_STRING) + error_str = place; + if (rv == ERROR_SYSLOG && where != ERROR_SYSLOG) + closelog(); + if (where == ERROR_SYSLOG && rv != ERROR_SYSLOG) + { +#ifdef LOG_DAEMON + openlog("metad", LOG_PID, LOG_DAEMON); +#else + openlog("metad", 0); +#endif + } + return rv; +} + +void error(char *mesg, char *a, char *b, char *c) +{ + char buf[1024]; + + sprintf(buf, mesg, a, b, c); + + switch(error_dest) + { + case ERROR_STDERR: + fprintf(stderr, "metad: %s\n", buf); + break; + case ERROR_STRING: + if (*error_str == NULL) + { + *error_str = (char*)malloc(err_str_len=(strlen(buf)+100)); + strcpy(*error_str, buf); + } + else if (strlen(*error_str)+strlen(buf)+1 > err_str_len) + { + *error_str = (char*)realloc(*error_str, err_str_len += strlen(buf)+100); + strcat(*error_str, buf); + } + else + strcat(*error_str, buf); + break; + case ERROR_SYSLOG: + syslog(LOG_ERR, "%s", buf); + break; + } +} + +#ifdef STDARGS +void log(int level,...) +#else +void log(va_alist) +va_dcl +#endif +{ + va_list pvar; + char buf[1024]; + char *format; + +#ifdef STDARGS + va_start(pvar, level); +#else + int level; + va_start(pvar); + level = va_arg(pvar, int); +#endif + format = va_arg(pvar, char *); + vsprintf(buf, format, pvar); + switch(error_dest) + { + case ERROR_STDERR: + fprintf(stderr, "metad: %s\n", buf); + break; + case ERROR_STRING: + if (*error_str == NULL) + { + *error_str = (char*)malloc(err_str_len=(strlen(buf)+100)); + strcpy(*error_str, buf); + } + else if (strlen(*error_str)+strlen(buf)+1 > err_str_len) + { + *error_str = (char*)realloc(*error_str, err_str_len += strlen(buf)+100); + strcat(*error_str, buf); + } + else + strcat(*error_str, buf); + break; + case ERROR_SYSLOG: + syslog(level, "%s", buf); + break; + } +} + +void dolog(service_t sv, proc_t p, char *buf) +{ + log(LOG_INFO, "%s: %d: %s\n", sv->service, p->pid, buf); +} + diff --git a/loadstate.c b/loadstate.c new file mode 100644 index 0000000..620b508 --- /dev/null +++ b/loadstate.c @@ -0,0 +1,158 @@ + +/* + * load process state from a file. + * when metad reruns itself, it saves state in the form + * of a "list" output. + * we now read that it and add proc entries as appropriate + * + */ +#include "metad.h" +#include +#ifdef SOLARIS +#include +#else +#include +#endif +#include "skip.h" + +char *get_str(); +char *get_return(); +void qfree(char *a) +{ + if (a) free(a); +} + +void loadstate(int fd) +{ + int len = lseek(fd, 0, 2); + int b; + char *buf; + lseek(fd, 0, 0); + + buf = (char*)malloc(len); + read(fd, buf, len); + close(fd); + + init_recv(buf); + if (get_byte() != 3) return; /* something VERY wrong */ + b = get_byte(); + while(b==1 || b == 2) /* service */ + { + char *sname; + int enabled; + int args; + int class; + service_t sv, *svp = NULL; + + sname = get_str(); + if (sname) + { + svp = skip_search(services, sname); + free(sname); + } + get_int(); /* max */ + qfree(get_str()); /* home */ + qfree(get_str()); /* user */ + qfree(get_str()); /* crash */ + if (b == 2) + { + get_int(); /* watch_output */ + qfree(get_str()); /* pidfile */ + } + get_int(); /* cnt */ + enabled = get_int(); + qfree(get_str()); /* prog */ + args = get_int(); + while (args--) + qfree(get_str()); + class = get_byte(); + switch(class) + { + case 1: + get_int(); + get_int(); + get_int(); + break; + case 2: + get_int(); + get_int(); + get_int(); + get_int(); + break; + } + if (svp) sv= *svp ;else sv= NULL; + if (sv) sv->enabled = enabled; + b = get_byte(); + while (b == 3 || b == 4) /* process */ + { + int pid, start, xit; + int forkedpid = 0, pipefd = -1; + pid = get_int(); + if (b == 4) + { + forkedpid = get_int(); + pipefd = get_int(); + } + + start = get_int(); /* start */ + get_int(); /* hold */ + xit = get_int(); + get_int(); /* status */ + if ((sv && (xit == 0 && kill(pid, 0)==0)) || (xit>0 && forkedpid>0 && kill(forkedpid,0)==0) ) + { + proc_t p = (proc_t)malloc(sizeof(struct proc)); + p->pid = pid; + p->service = sv; + p->pipefd = pipefd; + p->bufptr = 0; + p->it_forked = forkedpid; + p->is_crash = 0; + p->start_time = start; + p->hold_time = xit?1:0; + p->exit_time = xit; + skip_insert(sv->proc_list, p); + skip_insert(allprocs, p); + } + b = get_byte(); + } + } + free(buf); +} + +extern char **gargv; +void restart(void) +{ + int fd; + int len; + char *buf; + service_t *svp; + char *file = "/var/tmp/...metad-temp-file"; + + fd = open(file, O_RDWR|O_TRUNC|O_CREAT, 0600); + if (fd < 0) + { + close(0); + execv(gargv[2], gargv); + exit(1); + } + unlink(file); + init_return(); + send_byte(3); /* listing */ + for (svp = skip_first(services) ; svp ; svp = skip_next(svp)) + { + send_service(*svp); + } + send_byte(0); /* finished */ + + buf = get_return(&len); + write(fd, buf, len); + if (fd > 0) + { + close(0); + dup(fd); + close(fd); + } + gargv[0] = "metad-restart"; + execv(gargv[2], gargv); + exit(1); +} diff --git a/mainloop.c b/mainloop.c new file mode 100644 index 0000000..c7ea270 --- /dev/null +++ b/mainloop.c @@ -0,0 +1,158 @@ + + +#include "metad.h" +#include +#include "skip.h" +#include + +/* + * main loop of metad + * wait for read-select on some sockets, or for time to pass + * or for a process to exit. + * when one of these happens, we check with each service to see + * if it wants to do something. + * + * services ask to do something with + * listenon(socket) + * waituntil(time) + * and can check is a socket is read with + * readyon(socket) + * + */ + + +fd_set wait_for, are_ready; +fd_set write_on, can_write; +time_t when_wake; + +void listenon(int socket) +{ + FD_SET(socket, &wait_for); +} + +int readyon(int socket) +{ + return FD_ISSET(socket, &are_ready); +} + +void writeon(int socket) +{ + FD_SET(socket, &write_on); +} + + + +int canwrite(int socket) +{ + return FD_ISSET(socket, &can_write); +} + +void waituntil(time_t when) +{ + if (when_wake == 0 || when_wake > when) when_wake = when; +} + +static struct +{ + pid_t pid; + int status; + time_t time; +} saved_pids[20] = { { 0 } }; + +static struct timeval select_tv; +static void collectchild() +{ + pid_t pid; + int status; + + if ((pid = waitpid(-1, &status, WNOHANG)) > 0) + { + int i; + for (i=0; i<20 ; i++) + if (saved_pids[i].pid == 0) + { + saved_pids[i].pid = pid; + saved_pids[i].status = status; + time(&saved_pids[i].time); + break; + } + } + select_tv.tv_usec = 0; + select_tv.tv_sec = 0; +} + +int is_saved_pid(pid_t pid) +{ + int i; + for (i=0; i<20; i++) + if (saved_pids[i].pid == pid) + return 1; + return 0; +} + +void main_loop() +{ + struct sigaction sa; + sigset_t ss; + sa.sa_handler = collectchild; + sa.sa_flags = 0; + sigemptyset(&ss); + sigaddset(&ss, SIGCLD); + sa.sa_mask = ss; + sigaction(SIGCLD, &sa, NULL); + + FD_ZERO(&are_ready); + FD_ZERO(&can_write); + while (1) + { + service_t *svp; + int i; + FD_ZERO(&wait_for); + FD_ZERO(&write_on); + when_wake = time(0)+5*60; /* always wakeup every 5minutes to look around... */ + collectchild(); /* incase signal was missed */ + + for (i=0 ; i<20 ; i++) + if (saved_pids[i].pid) + { + proc_t *pp; + pp = skip_search(allprocs, &saved_pids[i].pid); + if (pp) + { + (*pp)->status = saved_pids[i].status; + (*pp)->exit_time = saved_pids[i].time;; + select_tv.tv_sec = 0; + log(LOG_INFO, "process %d (%s) exited - status 0x%04x", saved_pids[i].pid, (*pp)->service->service, (*pp)->status); + } else + log(LOG_INFO, "process %d exited - status 0x%04x", saved_pids[i].pid, saved_pids[i].status); + saved_pids[i].pid = 0; + + } + + for (svp = skip_first(services) ; svp ; svp = skip_next(svp)) + check_service(*svp); + check_control(); + are_ready = wait_for; + can_write = write_on; + + if (when_wake) + { + time_t now; + time(&now); + select_tv.tv_sec = when_wake - now; + select_tv.tv_usec = 0; + if (when_wake < now) select_tv.tv_sec = 0; + } + else + { + select_tv.tv_sec = 100000; + select_tv.tv_usec = 0; + } + if (select(FD_SETSIZE, &are_ready, &can_write, NULL, &select_tv) <= 0) + { + FD_ZERO(&are_ready); + FD_ZERO(&can_write); + } + } +} + diff --git a/meta.c b/meta.c new file mode 100644 index 0000000..29205be --- /dev/null +++ b/meta.c @@ -0,0 +1,244 @@ + +/* send commands to metad's + * -V display meta version and metad versions + * + * -u send datagrams, don't use stream connection + * -b broadcast datagrams on all interfaces + * + * -r service run the service + * -a args pass args with previous run command + * -k service kick(run) the service + * -l service list the service + * -L list all services + * -D service disable service + * -E service enable service + * -K pid/service kill process/service (Sig 15) + * -R reread config + * -X tell metad to exit + * -v be verbose + * -C command send the given command + * + * -B actually send broadcast requests + * + * other arguments are host names + * + */ + +#include +#include +#include "metad.h" +#include "args.h" +#include "dlink.h" + +extern char version[]; +char *progname; + +void usage(char *fmt, char *arg) +{ + char buf[1024]; + sprintf(buf, fmt, arg); + fprintf(stderr,"%s: %s\n", progname, buf); + fprintf(stderr,"Usage: %s -[h?Vubv] -[LRXB] {-[rklDEK] service} [-C command] hostlist ...\n", progname); +} + +void help() +{ + printf( + "Usage: %s -[h?Vubv] -[LRXB] {-[rklDEK] service} [-C command] hostlist ...\n" + " -h This help message\n" + " -? As above\n" + " -V Print version number of metac and the metad running on any\n" + " host listed\n" + " -u Send commands in udp datagrams. This is faster than the\n" + " normal tcp, but\n" + " avoids delays on dead machines.\n" + " -b Send each request as a broadcast on any available network\n" + " interfaces.\n" + " -v Be verbose when listing services.\n" + " -L List all services supported by the metad on the remote host.\n" + " -R Tell metad to reread it's configuration file.\n" + " -X Tell metad to eXit.\n" + " -B Tell the metad to rebroadcast the request on it's\n" + " interfaces.\n" + " -r service Try to start a process running for the service.\n" + " -k service Kick a service into action, same as above.\n" + " -l service List details of the named service.\n" + " -D service Disable a service. No more processes will be started for\n" + " that service.\n" + " -E service Enable a service. Metad will start processes as\n" + " appropriate.\n" + " -K pid Of metad is managing a process with number pid, then it\n" + " is sent a SIGTERM.\n" + " -K service Every process running for the given service is sent a\n" + " SIGTERM.\n" + " -C command Send the given command (which will usually be quoted).\n" + " Commands are:\n" + " run service args like -r, but allows args to be passed to\n" + " the process.\n" + " kill signum pid-or-service like -K, but allows a signal number\n" + " to be given.\n" + " restart tell metad to re-exec itself, for use when a\n" + " new version is installed\n" + " (plus others that duplicate the above flags)\n" + , progname) ; +} + +int send_cmd(char *cmd, int udp, char *host, int verbose); + +void main(int argc, char *argv[]) +{ + void *cmds = dl_head(); + void *hosts = dl_head(); + int local_broad = 0; + int remote_broad = 0; + int use_dgram = 0; + int show_version = 0; + int show_help = 0; + int verbose = 0; + char *c, *c2; + void *opts = args_init(argc, argv, "??h?V?u?b?r:a:k:l:L?D:E:K:R?X?v?C:B?"); + int opt; + char *arg; + int pos, inv; + progname = strrchr(argv[0], '/'); + if (progname) progname++; else progname = argv[0]; + + while ((opt = args_next(opts, &pos, &inv, &arg))!= END_ARGS) + switch(opt) + { + case BAD_FLAG: + usage("Bad option: %s", argv[pos]); + exit(1); + case NO_FLAG: + c = dl_strdup(arg); + dl_add(hosts, c); + break; + case 'h': + case '?': + show_help = 1; + break; + case 'V': /* my version number */ + printf("%s: version %s\n", progname, version); + show_version =1; + break; + case 'u': + use_dgram = 1; + break; + case 'b': + local_broad = 1; + break; + case 'r': + case 'k': + c = dl_strndup("kick ", 5+strlen(arg)+1); + strcat(c, arg); + dl_add(cmds, c); + break; + case 'a': + c = dl_prev(cmds); + if (c == cmds || (strncmp(c, "kick ", 5)!= 0 && strncmp(c, "run ", 4)!= 0)) + { + usage("%s: -a only valid after -r or -k", NULL); + exit(1); + } + c2 = dl_strndup(c, strlen(c)+1+strlen(arg)+1); + strcat(c2, " "); + strcat(c2, arg); + dl_del(c); dl_free(c); + dl_add(cmds, c2); + break; + case 'l': + c = dl_strndup("list ", 5+strlen(arg)+1); + strcat(c, arg); + dl_add(cmds, c); + break; + case 'L': + c = dl_strdup("list"); + dl_add(cmds, c); + break; + case 'D': + c = dl_strndup("disable ", 8+strlen(arg)+1); + strcat(c, arg); + dl_add(cmds, c); + break; + case 'E': + c = dl_strndup("enable ", 7+strlen(arg)+1); + strcat(c, arg); + dl_add(cmds, c); + break; + case 'K': + c = dl_strndup("kill 15 ", 8+strlen(arg)+1); + strcat(c, arg); + dl_add(cmds, c); + break; + case 'R': + c = dl_strdup("reread"); + dl_add(cmds, c); + break; + case 'X': + c = dl_strdup("die"); + dl_add(cmds, c); + break; + case 'v': + verbose = 1; + break; + case 'C': + c = dl_strdup(arg); + dl_add(cmds, c); + break; + case 'B': + remote_broad = 1; + break; + } + + if (show_help) + { + help(); + exit(0); + } + if (dl_next(hosts) == hosts && local_broad == 0) + { + /* no where to send to... */ + if (dl_next(cmds) != cmds) + { + fprintf(stderr,"%s: commands were specified with no where to send them!\n", progname); + exit(1); + } + /* nothing to do, though might has displayed version. + * Should probably give usage... + */ + if (show_version == 0) + usage("Nothing to do", NULL), exit(1); + exit(0); + } + if (dl_next(hosts) != hosts && local_broad) + { + fprintf(stderr, "%s: you probably don't want to broadcast AND list hosts...\n", progname); + exit(1); + } + + if (show_version) + { + c = dl_strdup("version"); + dl_insert(cmds, c); + } + for (c= dl_next(cmds) ; c != cmds ; c = dl_next(c)) + { + char *cmd = (char*)malloc(10 + strlen(c)); /* make sure there is room for port and broad */ + char *h; + + strcpy(cmd, "1 "); + if (remote_broad) + strcat(cmd, "BROAD "); + strcat(cmd, c); + if (local_broad) + broadcast(cmd); + else for (h=dl_next(hosts) ; h != hosts ; h = dl_next(h)) + { + if (strcmp(h, ".")== 0) + send_cmd(cmd, use_dgram, "localhost", verbose); + else + send_cmd(cmd, use_dgram, h, verbose); + } + } + exit(0); /* FIXME what exit status */ +} diff --git a/metad.c b/metad.c new file mode 100644 index 0000000..cd44de0 --- /dev/null +++ b/metad.c @@ -0,0 +1,33 @@ + +#include "metad.h" +#include +#include +#ifdef SOLARIS +#include +#endif +char **gargv; + +void main(int argc, char *argv[]) +{ + gargv = argv; + + { + int ttyfd = open("/dev/tty", 2, 0); + if (ttyfd >= 0) { + ioctl(ttyfd, TIOCNOTTY, NULL); + close(ttyfd); + } + } + + /* FIXME should make sure stdin/stdout/stderr are open to something */ + service_init(); + control_init(); + errors_to(ERROR_SYSLOG, NULL); + read_config(services, argv[1]); + if (strcmp(argv[0], "metad-restart")==0) { + loadstate(0); + open("/dev/null", O_RDONLY); + } + main_loop(); + /* NOT REACHED */ +} diff --git a/metad.h b/metad.h new file mode 100644 index 0000000..395525c --- /dev/null +++ b/metad.h @@ -0,0 +1,145 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +/* hold config file info */ +#ifdef ULTRIX +char *strdup(char*); +#endif + +typedef struct service { + char *service; /* name of service */ + struct class *class; /* pointer to class*/ + void *classinfo; /* class specific options */ + /* class independant options */ + + int max_proc; /* max number of processes */ + char *crash_prog; /* prog to call when process crashes */ + char *home_dir; /* directory to run process in */ + char *username; /* who to run process as */ + char *pidfile; /* file to read process id from after child exits */ + int watch_output; /* if true, attatch a pipe to std{out,err} and what it */ + int enabled; /* whether to start enabled */ + int start_cnt; + char *program; /* program to run */ + char **args; /* arguments */ + + int pending; /* set before reprocessing config file, cleared when found in file */ + + void *proc_list; /* skiplist of currently active processes */ + time_t next_hold; /* hold time for next crashing processes */ +} *service_t; + +typedef struct class { + char *class; /* name of class */ + int (*c_process_opt)(); /* function to processes options */ + void (*register_service)(); /* register the service if necessary */ + void (*c_check_service)(); /* check if anything needs to be done for a service */ + void (*copy_state)(); /* copy state from old service struct to new */ + void (*free_state)(); /* free class dependant state */ + void (*send_class)(); /* send class info */ + void (*disable_service)(); /* unregister service */ + void (*new_parent)(); /* in parent of new child */ + void (*new_child)(); /* in a new child */ + int (*prefork)(); /* just before fork */ +} *class_t; + +typedef struct proc { + pid_t pid; /* pid of process */ + service_t service; + int pipefd; /* if a pipe was conencted to stdout/stderr */ + char pipebuf[300]; /* buffer lines of data from pipefd before syslogging */ + int bufptr; /* how full buf is */ + int it_forked; /* true if process fork/exited, and we have pid from pidfile */ + int is_crash; /* if cleaning up after core dump */ + time_t start_time; /* when processes started */ + time_t hold_time; /* time to let go of processes slot - set if process crashes early */ + time_t exit_time; /* the time that it exited */ + int status; /* wait status if exit_time > 0 */ +} *proc_t; + + +typedef struct daemon_info { /* class specific info for class "daemon" */ + int min_proc; /* minimum number of processes to have running */ + int period; /* period in seconds for restarts */ + + time_t last_restart; /* last time a periodic restart was attempted */ +} *daemon_info_t; + + +typedef struct commands { + char * name; + void (*proc)(); +} *commands_t; + +class_t find_class(char *name); +void loadstate(int fd); +int do_command(char **args, char *host, void *con); +int tcp_port(); +int udp_port(); +int control_init(void); +void check_control(void); +#ifndef IN_CONTROL +int set_reply(void *con, char *reply, int len); +int return_error(void *con, char *fmt, ...); +#endif +void listenon(int socket); +int readyon(int socket); +void writeon(int socket); +int canwrite(int socket); +void waituntil(time_t when); +void main_loop(void); +int read_config(void *services, char *file); +int count_procs(service_t sv); +void check_service(service_t sv); +int new_proc(service_t sv, char **env); +service_t find_service(char *name); +service_t new_service(char *name, class_t class); +int process_opt(service_t sv, char *opt); +void service_init(void); +void free_service(service_t sv); +void broadcast(char *line); +void init_return(void); +void send_byte(int b); +int get_byte(void); +void send_int(int i); +int get_int(void); +void send_str(char *); +void send_service(service_t); +void do_send(void *); +void control_close(void); +void prepare_restart(void); +void restart(void); +void init_recv(char*); +char *prtime(time_t); +int udp_port(void); +int tcp_port(void); + +int strccmp(char*, char*); +#ifndef IN_ERROR +void error(char *mesg,...); +void log(int, char*, ...); +#endif +#define ERROR_STDERR 0 +#define ERROR_SYSLOG 1 +#define ERROR_STRING 2 +int errors_to(int where, char **place); + +char **strlistdup(char **l); +char **strsplit(char *line, char *sep); +/*char *strdup(char*);*/ + +void dolog(service_t, proc_t, char *); + +extern void *services; +extern void *allprocs; + + +time_t time(time_t*); + +extern int is_saved_pid(pid_t pid); diff --git a/metad.texi b/metad.texi new file mode 100644 index 0000000..fa222c9 --- /dev/null +++ b/metad.texi @@ -0,0 +1,585 @@ +\input texinfo @c -*-texinfo-*- +@c %**start of header +@setfilename metad.info +@settitle Metad -- The Daemon for controlling daemons. +@setchapternewpage off +@c %**end of header + +@ifinfo + +@node Top, Introduction, (dir), (dir) +@comment node-name, next, previous, up +@top Metad + +@end ifinfo + +@titlepage +@title METAD +@subtitle The Daemon for controlling Daemons +@author The School of Computer Science and Engineering +@author Computing Support Group +@end titlepage + +@menu +* Introduction:: +* Configuration:: +* Control:: +* Logs:: + + --- The Detailed Node Listing --- + +Introduction + +* Functionality:: +* Usefulness:: + +Functionality + +* Service Classes:: + +Configuration + +* Invocation:: +* Config file format:: +* General options:: +* The Daemon Class:: +* The Listener Class:: +* The Stream Class:: + +Config file format + +* Service Name and Class:: +* Options:: +* Program and arguments:: + +General options + +* Max:: +* Dir:: +* User:: +* Pidfile:: +* Watch_output:: +* Enabled:: +* Crash:: + +The Daemon Class + +* Min:: +* Period:: + +Control + +* Disabling and Enabling:: +* Killing:: +* Starting:: +* Listing:: +* Reread and Restart:: +* Broadcast:: +* metac:: +@end menu + +@node Introduction, Configuration, Top, Top +@chapter Introduction + +@emph{Metad} is a program for running other programs. +Unlike the shell (which also satisfies that description), Metad runs +background system programs rather than foreground user programs. + +Metad serves a purpose similar to @code{cron}, @code{inetd}, and +@code{init}, and could replace them all. +The significant enhancement metad has over these programs is that it is +easily controllable, from anywhere on the network. + +@menu +* Functionality:: +* Usefulness:: +@end menu + + +@node Functionality, Usefulness, Introduction, Introduction +@section Functionality + +The basic function of metad is to run programs in response to certain +stimuli. +These stimuli can be time passing, other programs exiting, I/O +activity being possible on some socket, or direct request. + +Metad will also, on request, send signals to any of the programs that it +has run, report on its status, and disable or re-enable any of the +services it offers. The requests are sent to metad over the network by +the @code{metac} program described below in @xref{metac}. + +Metad gets a list of services that it should provide from a +configuration file (@pxref{Config file format}). This file describes, +for each service, +what program to run, how to run it, and when to run it. + +The @emph{what} part is simply the name of the file containing the +program. +The @emph{how} part includes command line arguments, working directory +and user id. +The @emph{when} part is probably most interesting. + +@menu +* Service Classes:: +@end menu + +@node Service Classes, , Functionality, Functionality +@subsection Service Classes +Meta understands a small number of types or @dfn{classes} or service, +each of which respond to different stimuli. +@table @code +@item daemon +The @code{daemon} is the simplest form for service +(and the only one currently implemented). +A @code{daemon} service will run its program either repeatedly, +periodically, on request, or any combination of these. +It may allow multiple instances of the program to be running at once, +and can impose an upper and lower limit on this number. + +It may also be able to run its program just once (at startup) or on +completion of some other services program (not implemented yet). + +@item listener +The @code{listener} class supports programs which need to listen for input on +a network port. +Metad will set up the network port and will wait for activity. When +activity is noticed, metad will run the program, passing it the network +port, so that it can do what ever is needed. +When the program exits, metad will resume waiting for more activity. + +The port can be a udp/ip port or a tcp/ip port. + +This is similar to the dgram/wait service of @code{inetd} and the +stream/wait service of some implementations of @code{inetd}. + +@item steam +The @code{stream} class supports programs which need to respond to +network stream connections, such as @code{telnetd} or @code{fingerd}. +Metad will listen on a stream socket for a connection request. +It will then accept the connection, possibly verify the source, and then +run the program passing the the connection. + +As with all metad services, it is possible to limit the number of +program instances which are running concurrently. + +@end table + +@node Usefulness, , Functionality, Introduction +@section Usefulness + +The main goal that metad was developed to meet, was to assist in the +development of various daemon services which needed to be run on various +machines around the network. +The sort of assistance required involved: +@itemize @bullet +@item +Restarting a daemon if it dies, possibly reporting the death. +@item +Killing a restarting a daemon when a new version is installed. +@item +Taking control of new daemons. +@item +Running some program on all machines on the network. +@end itemize + +This goal lead to the @emph{daemon} class and the @emph{metac} control +program being created. +The @emph{listener} and @emph{stream} classes are later additions +brought about due to a dissatisfaction with inetd. + +Say something about shortcomings?? hiding failure and not being able to +control named. + +@node Configuration, Control, Introduction, Top +@chapter Configuration + +@menu +* Invocation:: +* Config file format:: +* General options:: +* The Daemon Class:: +* The Listener Class:: +* The Stream Class:: +@end menu + +@node Invocation, Config file format, Configuration, Configuration +@section Invocation + +Metad takes precisely two command line arguments. +The first is used as the name of the configuration file, as described +below. +The second is used as the name of the file which contains the metad +program. +This is used when metad is asked to restart itself (for instance, when a +new version has been installed). + +Metad also inspects its "zeroth" argument. If this is the string +@samp{metad-restart}, then metad assumes that it has just restarted +itself and looks on @strong{stdin} for a description of the previous state +(in particular, what processes were running). + + +@node Config file format, General options, Invocation, Configuration +@section Config file format + +The configuration file for metad simply lists the different services +that metad should monitor, one per line. +Any line that is not empty or a comment line (beginning with a hash or +number sign @samp{#}) is a service line. +Each line is broken up into words on spaces and tabs. Spaces and tabs +can be preserved in words by quoting them with single or double quotes. +Quotes and back slashes can be preserved by prefixing them with back +slashes. + +The words are the services name, its class, some options, the program +name, and the programs arguments. +The program name is distinguished from options by the fact that it +starts with a slash. + +@menu +* Service Name and Class:: +* Options:: +* Program and arguments:: +@end menu + +@node Service Name and Class, Options, Config file format, Config file format +@subsection Service Name and Class + +The service name is any unique string of characters and is +uninterpreted. +In the future, services may be grouped based on a common prefix of the +name. This prefix would be up to a period. + +The class name is one of a predefined set of classes, currently only +@samp{daemon} is supported. + +@node Options, Program and arguments, Service Name and Class, Config file format +@subsection Options + +The options provide details one how and when a service will run a +program. +Some options are applicable to all classes of service. These are the +General options described below (@pxref{General options}). +Others are specific to a class and are detail under the particular class +below. + +Most options take the form of @samp{@var{name}=@var{value}}. + +@node Program and arguments, , Options, Config file format +@subsection Program and arguments +As mentioned, the program is the first word on the line which begins +with a slash. The full path name of the program must be given. +Subsequent words are passed to the program as an argument list. There +must always be at least one argument, which is normally the name of the program. + +@node General options, The Daemon Class, Config file format, Configuration +@section General options + +The General options are applicable to all classes of service. The +default value can be different with different services. +The default listed below is the default used for the @code{daemon} service. +@menu +* Max:: +* Dir:: +* User:: +* Pidfile:: +* Watch_output:: +* Enabled:: +* Crash:: +@end menu + +@node Max, Dir, General options, General options +@subsection Max + +The @dfn{max} option sets the maximum number of concurrent processes +that can be running for a particular service. The default for +@code{daemon}s is one. Format is @code{max=@var{nn}} when @var{nn} is a +decimal integer. + +@node Dir, User, Max, General options +@subsection Dir + +The @dfn{dir} option sets the working directory for processes run for +this service. The default is the same as metad's working directory. +A full path name should be given. + +@node User, Pidfile, Dir, General options +@subsection User + +The @dfn{user} option specifies an alternate userid to for processes run +for this service. Metad must be run by @strong{root} for this to work. +The default is to leave the user id unchanged from that of metad. + +When setting the userid, the groups ids are also set to those for the +named user. + +@node Pidfile, Watch_output, User, General options +@subsection Pidfile + +Some daemon programs insist on forking and continuing to perform useful +work in the child while the parent exits. This is useful in many +circumstances, but makes it difficult for metad to continue to monitor +and control the program. +To help with this, the @dfn{pidfile} option specifies the name of a file which the program +might write the process id of the forked child. + +When a process that metad has started exits, metad checks the +appropriate @var{pidfile} to see if it was written since the process +started, and if it contains the pid of an active process. +If this check succeeds, then metad assumes that the process found is +carrying one the work of the original process. + +This process will then be the target of any @strong{kill} commands, and +it will be treated just as though it were the original process. + +@node Watch_output, Enabled, Pidfile, General options +@subsection Watch_output + +If the @dfn{watch_output} option is listed for a service, then metad +will connect the standard output and standard error streams of any +processes started for that service to a pipe. +Metad will monitor the pipe for two purposes. + +@enumerate +@item +Any data received from the pipe will be logged with syslog as an +informational message about the processes. +@item +Metad will assume that the program is still active until an end-of-file +is read on the pipe --- even if the process that metad first started exits. +@end enumerate + +@node Enabled, Crash, Watch_output, General options +@subsection Enabled + +The @dfn{enabled} option can be used to enabled or disable a service at +startup. The value can be one of the words @samp{yes} and @samp{no}. The +default value is @samp{yes}. + +@node Crash, , Enabled, General options +@subsection Crash + +The @dfn{crash} option specifies a program to run after a crash (exit +due to signal). The program is expected to clean up or report the +problem. +Currently this is unimplemented so details are scarce. + +@node The Daemon Class, The Listener Class, General options, Configuration +@section The Daemon Class + +The @code{daemon} class understands two more options. They are @samp{min} +and @samp{period}. +@menu +* Min:: +* Period:: +@end menu + +@node Min, Period, The Daemon Class, The Daemon Class +@subsection Min + +The @dfn{min} option specifies the minimum number of processes that +should be running for the service. If fewer than that minimum are +running, new processes will be started until the minimum is reached. + +The default minimum is zero. + +@node Period, , Min, The Daemon Class +@subsection Period + +The @dfn{period} option specifies how frequently a daemon service should +start a new processes. The value should be a decimal integer with a +single character suffix. +Suffixes are @samp{s} for seconds, @samp{m} for minutes, @samp{h} for +hours, or @samp{d} for days. If no suffix is given, seconds is assumed. + +If this period passes without a processes being start (or attempted to +be started), metad will attempt to start a process to execute the +services program. If the maximum number of processes are already +running, the attempt will fail, but will still be recorded as an attempt +and metad will wait for the period to pass again. + +@node The Listener Class, The Stream Class, The Daemon Class, Configuration +@section The Listener Class + +The Listener class is not implemented yet, so details are scarce. +It is expected to need a port option (service/protocol, e.g. pop3/tcp). + +@node The Stream Class, , The Listener Class, Configuration +@section The Stream Class + +The Stream class is not yet implemented, so details are not available. +It is expected to need a port option (service/protocol e.g. telnet/tcp) +and multiple access options (access+.domain access-@@netgroup +access+nnn.nnn/16) + +The identity of the peer will probably be provided in the environment, +e.g. METAD_PEER=host.domain.au:port + + +@node Control, Logs, Configuration, Top +@chapter Control + +A particularly useful feature of metad is the fact that it can be +controlled from anywhere on the network. +To achieve this, metad understands a fairly simple command protocol. +Commands can be sent either over a tcp/ip connection, in which case a +response is returned, or via a udp/ip datagram. The use of datagrams +means that a command can be broadcast to all metads on a given subnet. + +As one of the commands the metad will obey is to rebroadcast a command, +commands can easily be broadcast around a medium sized internet. + +@menu +* Disabling and Enabling:: +* Killing:: +* Starting:: +* Listing:: +* Reread and Restart:: +* Broadcast:: +* metac:: +@end menu + +@node Disabling and Enabling, Killing, Control, Control +@section Disabling and Enabling + +Individual services can be disabled and re-enabled. + +When disabled a service will not start any new processes, though it will +not attempt to stop and processes that are already running. +A disabled service that would normally listen for some specific network +activity will not listen for that activity at all. + +Services are disabled be sending the command @samp{disable +@var{servicename}} and are enabled with the command @samp{enabled +@var{servicename}}. + +These can be easily sent with metac using option @samp{-D @var{service}} +and @samp{-E @var{service}}. + +@node Killing, Starting, Disabling and Enabling, Control +@section Killing + +Killing a process involves sending a Unix signal to the process. This +does not always cause it to die. + +To send a signal to all processes for a particular service, the command +is @samp{kill @var{signum} @var{service}}. +To send to a specific process, its process id must be given instead of +the servicename. + +Sending signal 15 (SIGTERM) can easily be done with metac using @samp{-K +@var{service}} or @samp{-K @var{processid}}. + +@node Starting, Listing, Killing, Control +@section Starting + +Some classes (currently only daemon, definitely not stream, probably +listener) will start a new process on request. This request is sent with +the @samp{run} command. +Optional extra information can be sent with the run command to modify +the action of the started program. This is passed the the program +through the environment variable @emph{METAD_ARG}. +The command to run a new process is +@samp{run @var{service} @var{extra-information}}. + +If no extra information is to be sent, this can be done with metac using +@samp{-r @var{service}}. To send extra information simply add @samp{-a +@var{args}}. + + +@node Listing, Reread and Restart, Starting, Control +@section Listing + +Metad can be asked to send a report on the services it is maintaining +including the current processes which are running. The information is +returned in a binary format which is only understood by metac. +Either a single service can be listed (command @samp{list @var{service}}, option +@samp{-l @var{service}}) or all services can be listed. +(command @samp{list}, option @samp{-L}). + +I should say something about the listing that is returned... later. + +@node Reread and Restart, Broadcast, Listing, Control +@section Reread and Restart + +The @samp{reread} command causes metad to reread its config file. +New services can be added and options, programs and arguments can be +changed. If a service has be removed from the config file, then the +service is not forgotten by metad (it might have running processes) but +it is disabled. + +The @samp{restart} command causes to metad to exec a new instance of +itself, replacing the old one. +Information about what processes are running for which service is passed +through so that there will be no undue interruption of +service. Processes for services which no longer exist will be simply +forgotten. + +This is useful after installing a new version of metad. + +@node Broadcast, metac, Reread and Restart, Control +@section Broadcast + +The @samp{broadcast} command will broadcast it arguments as a command to +all nearby metads (on the same subnet). This may cause the command to be +delivered to some metads multiple times (if they are on machines with +multiple interfaces). As most metad commands are fairly idempotent, this +is not likely to be a problem, but should be considered. + +@node metac, , Broadcast, Control +@section metac + +Metac is the program to use to send commands to metad. +It takes are arguments a number of commands and a number of hosts to +send the commands to. It may be asked to broadcast the commands, or to +send them via datagrams. The default it to send them over a stream +connection. + +Commands and host names may be intermixed on the command line. All +commands will still be sent to all hosts. + +Commands can be given explicitly using the @samp{-C} flag, or more +briefly using one of the command flags. +The flags understood by metac are: +@table @samp +@item -u +Send (udp) datagrams, rather than making stream connection. +@item -b +Broadcast datagrams on all network interfaces. +@item -B +All commands should be sent with a @samp{broad } prefix so that the +responding metad will broadcast the command in its subnet. +@item -v +Be verbose in reporting responses --- what does this mean??? +@item -C command +Send the given command as is. +@item -r service +Send a @samp{run @var{service}} command. +@item -a args +Append the @var{args} to the previous @samp{-r} command; +@item -l service +Ask for a listing of the named service. +@item -L +Ask for a listing of all services. +@item -D service +Send a @samp{disable @var{service}} command. +@item -E service +Send a @samp{enable @var{service}} command. +@item -K pid/service +Send a @samp{kill 15 @var{pid/service}} command to kill the process or +service. +@item -R +Send a @samp{reread} command. +@item -X +Send an @samp{exit} command. (but thats not documented...);. +@item -V +Display version number of metac and send request for version information +to metads. +@end table + +@node Logs, , Control, Top +@chapter Logs + +@contents +@bye diff --git a/nargs.c b/nargs.c new file mode 100644 index 0000000..f831299 --- /dev/null +++ b/nargs.c @@ -0,0 +1,117 @@ + +/* + * args - a getopt link argument processor + * + * void *handle = args_init(argc, argv, "argctl"); + * + * args_next(handle, &pos, &inv, &optarg) -> BAD_FLAG/END_ARGS/NO_FLAG/opt + * + * argctl = {}* + * typechar == ? -f or -~f + * + -f arg or -~f + * - -f or -~f arg + * : -f arg only + * + * + */ + +#define IN_ARGS +#include "args.h" +struct handle +{ + int argc; + char **argv; + char *ctl; + int nextarg, nextchar; +}; + +struct handle *args_init(int argc, char **argv, char *ctl) +{ + /* if ctl is bad, return NULL, else a handle */ + int i; + handle *h; + for (i=0 ; ctl[i]; i+= 2) + if (ctl[i+1] != '?' && ctl[i+1] != '-' && ctl[i+1] != '+' && ctl[i+1] != ':') + break; + if (ctl[i]) + return NULL; + + h = (struct handle*)malloc(sizeof(struct handle)); + if (h == NULL) + return h; + h->argc = argc; + h->argv = argv; + h->ctl = ctl; + h->nextarg = 1; + h->nextchar = 0; + return h; +} + +int args_next(struct handle *h, int *pos, int *inv, char **opt) +{ + int invc = 0; + int invfound = 0; + int i; + char *a; + if (h->nextarg >= h->argc) + return END_ARGS; + if (h->nextchar == 0) + { + if (h->argv[h->nextarg][0] != '-') + { + if (pos) *pos = h->nextarg; + if (inv) *inv = 0; + if (opt) *opt = h->argv[h->nextarg]; + h->nextarg++; + return NO_FLAG; + } + h->nextchar++; + } + a = h->argv[h->nextarg]; + while (a[h->nextchar] == '~') + { + invc = !invc; + invfound = 1; + h->nextchar++; + } + for (i=0 ; h->ctl[i] ; i+=2) + if (h->ctl[i] == a[h->nextchar]) + break; + if (h->ctl[i] == 0) + { + h->nextchar = 0; + h->nextarg ++; + return BAD_FLAG; + } + h->nextchar++; + if (pos) *pos = h->nextarg; + if (inv) *inv = invc; + if (a[h->nextchar] == 0) + { + h->nextchar = 0; + h->nextarg ++; + } + switch(h->ctl[i+1]) + { + case '?': + return h->ctl[i]; + case '+': + if (inv) + return h->ctl[i]; + break; + case '-': + if (!inv) + return h->ctl[i]; + break; + case ':': + break; + } + /* need optarg */ + if (h->nextarg >= h->argc) + return BAD_FLAG; + if (opt) + *opt = h->argv[h->nextarg] + h->nextchar; + h->nextarg++; + h->nextchar=0; + return h->ctl[i]; +} diff --git a/ports.c b/ports.c new file mode 100644 index 0000000..34ba265 --- /dev/null +++ b/ports.c @@ -0,0 +1,29 @@ + +#include +#include +#include +#include /* for hton? */ + +int tcp_port() +{ + struct servent *sv; + if (geteuid() > 0) + return htons(6110); + sv = getservbyname("metad","tcp"); + if (sv) + return sv->s_port; + else + return htons(611); +} + +int udp_port() +{ + struct servent *sv; + if (geteuid() > 0) + return htons(6110); + sv = getservbyname("metad","udp"); + if (sv) + return sv->s_port; + else + return htons(611); +} diff --git a/prtime.c b/prtime.c new file mode 100644 index 0000000..bca343c --- /dev/null +++ b/prtime.c @@ -0,0 +1,38 @@ + +#include +#include +#include + + +#define WEEKS (DAYS * 7L) +#define DAYS (HOURS * 24L) +#define HOURS (MINUTES * 60L) +#define MINUTES 60L + +#ifndef HZ +# define HZ 50 +#endif + +/* static function declarations */ + + +char *prtime(time_t time) +{ +static char tim[9]; + if (time >= WEEKS) { + sprintf(tim,"%4ldw%02ldd", time / WEEKS, (time % WEEKS) / DAYS); + } else if (time >= DAYS) { + sprintf(tim,"%4ldd%02ldh", time / DAYS , (time % DAYS) / HOURS); + } else if (time >= HOURS) { + sprintf(tim,"%4ldh%02ldm", time / HOURS , (time % HOURS) / MINUTES); + } else if (time >= MINUTES) { + sprintf(tim,"%4ldm%02lds", time / MINUTES , time % MINUTES); + } else + sprintf(tim,"%7ds", (int)time); + return tim; +} + +char *prticks(time_t ticks) +{ + return prtime(ticks/HZ); +} diff --git a/publish b/publish new file mode 100644 index 0000000..cb0c945 --- /dev/null +++ b/publish @@ -0,0 +1,16 @@ +#!/usr/local/bin/ae + +dest=/home/www/lib/html/doc/metad +src=metad +echo "Publishing $src.texi into $dest..." +rm $dest/* +cp $src.texi $dest +cd $dest || exit 1 + +texi2html -menu -split_chapter $src.texi +texi2dvi $src.texi +dvips $src.dvi > $src.ps + +ln -s ${src}_toc.html index.html + +chmod a+r * diff --git a/read_config.c b/read_config.c new file mode 100644 index 0000000..f9c003e --- /dev/null +++ b/read_config.c @@ -0,0 +1,148 @@ + +#include "metad.h" +#undef NULL +#include +#include + +/* read the config file + * + * first mark all services as pending + * + * then for each line which is not a blank/comment + * strplit and find name/class + * if name already exists: + * if same class, set attributes + * if different class, rename old service and create new + * else + * create new service if remainder parses ok + * + * if any remaining services are pending, disabled them unless there were errors + * + */ + +int read_config(void *services, char *file) +{ + service_t *serv; + char linebuf[1024]; + FILE *f; + int err = 0; + static char *lastfile = NULL; + if (file) lastfile = file; + else file = lastfile; + + for (serv = skip_first(services) ; serv ; serv = skip_next(serv)) + (*serv)->pending = 1; + + f = fopen(file, "r"); + if (f == NULL) + { + error("cannot find config file %s", file); + return 1; + } + while (fgets(linebuf, sizeof(linebuf), f)!= NULL) + { + int len = strlen(linebuf); + char **words; + int w; + + if (len > 1000 && linebuf[len-1] != '\n') + { + error("line too long in config file"); + fclose(f); + return 1; + } + if (linebuf[len-1] == '\n') linebuf[--len] = 0; + + words = strsplit(linebuf, " \t\n"); + if (words) + { + for (w=0 ;words[w] ; w++) if (words[w][0]=='#') words[w]=NULL; + if (words[0] && words[1]) + { + /* not a comment */ + service_t *svp = skip_search(services, words[0]); + service_t sv; + class_t cl = find_class(words[1]); + if (svp != NULL) + { + if ((*svp)->class != cl) + { + /* different classes - rename old services */ + skip_delete(services, words[0]); + strcpy(linebuf, words[0]); + do { strcat(linebuf, ".old"); } while (skip_search(services, linebuf)!= NULL); + free((*svp)->service); + (*svp)->service = strdup(linebuf); + skip_insert(services, *svp); + svp = NULL; + } + } + if (cl == NULL) + { + error("unknown class %s", words[1]); + err ++; + continue; + } + sv = new_service(words[0], cl); + /* now process options */ + for (w=2 ; words[w] && words[w][0] != '/' ; w++) + { + int ok = process_opt(sv, words[w]); + if (ok == 0) + ok = sv->class->c_process_opt(sv, words[w]); + if (ok <= 0) + { + if (ok == 0) + error("unknown option: %s", words[w]); + else + error("error in option: %s", words[w]); + free_service(sv); + sv = NULL; + err++; + break; + } + } + if (sv == NULL) continue; + if (words[w]) + sv->program = strdup(words[w++]); + if (words[w]) + sv->args = strlistdup(words+w); + if (sv->program == NULL) + error("missing program name for service %s", words[0]), err++; + else if (sv->args == NULL) + error("missing program arguments for service %s", words[0]), err++; + else + { + if (svp) + { + proc_t *pp2; + service_t sv2 = *svp; + sv->enabled = sv2->enabled; + sv->proc_list = sv2->proc_list; + + /* change service pointer to the new structure (for aidan) */ + if (sv->proc_list) + for (pp2 = skip_first(sv->proc_list) ; pp2 ; pp2=skip_next(pp2)) + (*pp2)->service = sv; + + sv2->proc_list = NULL; + sv->class->copy_state(sv2, sv); + skip_delete(services, sv2->service); + free_service(sv2); + } + skip_insert(services, sv); + sv->class->register_service(sv); + sv = NULL; + } + if (sv) free_service(sv); + } + free(words[-1]); + free(words-1); + } + } + fclose(f); + for (serv = skip_first(services) ; serv ; serv = skip_next(serv)) + if ((*serv)->pending) + (*serv)->enabled = 0; + return err; +} diff --git a/recvlist.c b/recvlist.c new file mode 100644 index 0000000..a13b426 --- /dev/null +++ b/recvlist.c @@ -0,0 +1,37 @@ + +void *malloc(int); +char *recvbuf; +int recvptr; + +void init_recv(char *buf) +{ + recvbuf = buf; + recvptr = 0; +} + +int get_byte() +{ + return 0xff & recvbuf[recvptr++]; +} + +int get_int() +{ + int i = 0; + i = get_byte(); + i = (i<<8) | get_byte(); + i = (i<<8) | get_byte(); + i = (i<<8) | get_byte(); + return i; +} + +char *get_str() +{ + int l = get_int(); + char *s,*p; + if (l == 0) + return (char*)0; + p = s = (char*)malloc(l); + while (l--) + *p++ = get_byte(); + return s; +} diff --git a/sendcmd.c b/sendcmd.c new file mode 100644 index 0000000..7f115e3 --- /dev/null +++ b/sendcmd.c @@ -0,0 +1,324 @@ + +/* send a command to a metad somewhere + * if we use tcp, wait for reply and interpret it + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "skip.h" + +#include "metad.h" + +char *get_str(void); +#ifdef ULTRIX +char *strdup(char*); +#endif +/* cache addresses */ +typedef struct acache +{ + char *host; + struct sockaddr_in addr; +} *acache; + +extern char *progname; + +static int acmp(acache a, acache b, char *host) +{ + if (b) host=b->host; + return strcmp(a->host, host); +} +static void afree(acache a) +{ + free(a->host); + free(a); +} + + +static void *addr_cache = NULL; + +static void list_service(char *, int); + +int send_cmd(char *cmd, int udp, char *host, int verbose) +{ + + acache *cp, c; + if (addr_cache == NULL) + addr_cache = skip_new(acmp, afree, NULL); + + cp = skip_search(addr_cache, host); + if (cp != NULL) + c = *cp; + else + { + c = (acache)malloc(sizeof(struct acache)); + c->host = strdup(host); + c->addr.sin_port = udp ? udp_port() : tcp_port(); + c->addr.sin_addr.s_addr = inet_addr(host); + if (c->addr.sin_addr.s_addr > 0 && c->addr.sin_addr.s_addr < 0xffffffff) + { + c->addr.sin_family = AF_INET; + } + else + { + struct hostent *he; + he = gethostbyname(host); + if (he == NULL) + { + c->addr.sin_family = -1; + fprintf(stderr, "%s: unknown host %s\n", progname, host); + } + else + { + memcpy(&c->addr.sin_addr, he->h_addr_list[0], 4); + c->addr.sin_family = AF_INET; + } + } + skip_insert(addr_cache, c); + } + if (c->addr.sin_family != AF_INET) + return -1; + if (udp) + { + static int sock = -1; + + if (sock == -1) + { + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (geteuid() == 0) + { + int port; + struct sockaddr_in myaddr; + port = 600 + time(0)%400; + + memset(&myaddr, 0, sizeof(myaddr)); + myaddr.sin_family = AF_INET; + while (port > 500) + { + myaddr.sin_port = htons(port); + if (bind(sock, (struct sockaddr *)&myaddr, sizeof(myaddr))== 0) + break; + port --; + } + if (port == 500) + { + fprintf(stderr, "%s: cannot bind priv udp port...\n", progname); + } + } + } + sendto(sock, cmd, strlen(cmd)+1, 0, (struct sockaddr *)&c->addr, sizeof(c->addr)); + } + else + { + /* tcp - have to make a new connection each time */ + int sock; + static int port = 0; + char buf[8192]; /* FIXME autosize */ + int n; + int have = 0; + + if (port == 0) + port = 600 + time(0)%400; + + if (geteuid() == 0) + sock = rresvport(&port); + else + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) + { + fprintf(stderr, "%s: cannot bind socket!!\n", progname); + return -1; + } +#ifdef TCP_CONN_ABORT_THRESHOLD + { + int thresh; + thresh = 10*1000; + setsockopt(sock, IPPROTO_TCP, TCP_CONN_ABORT_THRESHOLD, (char*)&thresh, 4); + } +#endif +/* */ + if (connect(sock, (struct sockaddr *)&c->addr, sizeof(c->addr))!= 0) + { + fprintf(stderr, "%s: cannot connect to %s\n", progname, c->host); + close(sock); + c->addr.sin_family = -1; + return -1; + } + write(sock, cmd, strlen(cmd)+1); + shutdown(sock, 1); /* don't want to write no more */ + do + { + n = read(sock, buf+have, sizeof(buf)-1 - have); + if (n>0) have += n; + } while (n>0 && have < sizeof(buf)-1); + close(sock); + if (have <= 0) + return 0; /* probably OK, FIXME */ + buf[have] = 0; + switch(buf[0]) + { + case 0: return 0; /* definately ok */ + case 1: /* error message */ + fprintf(stderr, "%s: %s: %s\n", progname, c->host, buf+1); + return 1; + case 2: /* version number */ + fprintf(stdout, "%s: %s\n", c->host, buf+1); + return 0; + case 3: /* listing */ + init_recv(buf+1); + list_service(c->host, verbose); + /* FIXME */ + return 0; + + case 'm': /* old version number */ + fprintf(stdout, "%s\n", buf); + return 0; + default: /* old metad */ + /* FIXME */ + return 0; + } + } + return 0; +} + + +static void list_service(char *host, int verbose) +{ + int b; + b = get_byte(); + while (b == 1 || b == 2) /* a service */ + { + char *sname, *home, *user, *crash, *prog; + char **args; + int argc; + int max, cnt, enabled; + int i; + int class; + char *classname; + char *pidfile = NULL; + int watch_output = 0; + int min, period, last; + int proto, port, active, backlog; + + sname = get_str(); + max = get_int(); + home = get_str(); + user = get_str(); + crash = get_str(); + if (b == 2) + { + watch_output = get_int(); + pidfile = get_str(); + } + cnt = get_int(); + enabled = get_int(); + prog = get_str(); + argc = get_int(); + args = (char**)malloc((argc+1)*sizeof(char*)); + for (i=0 ; iit_forked > 0 || p->pipefd >= 0) + { + send_byte(4); + send_int(p->pid); + send_int(p->it_forked); + send_int(p->pipefd); + } + else + { + send_byte(3); /* process */ + send_int(p->pid); + } + send_int(p->start_time); + send_int(p->hold_time); + send_int(p->exit_time); + send_int(p->status); +} + +void send_service(service_t sv) +{ + int i; + proc_t *pp; + if (sv->watch_output || sv->pidfile) + send_byte(2); + else + send_byte(1); /* service */ + send_str(sv->service); + send_int(sv->max_proc); + send_str(sv->home_dir); + send_str(sv->username); + send_str(sv->crash_prog); + if (sv->watch_output || sv->pidfile) + { + send_int(sv->watch_output); + send_str(sv->pidfile); + } + send_int(sv->start_cnt); + send_int(sv->enabled); + send_str(sv->program); + for (i=0 ; sv->args[i] ; i++); + send_int(i); + for (i=0 ; sv->args[i] ; i++) + send_str(sv->args[i]); + (sv->class->send_class)(sv); + for (pp = skip_first(sv->proc_list) ; pp ; pp = skip_next(pp)) + send_proc(*pp); +} + +void send_int(int i) +{ + send_byte((i>>24)& 0xff); + send_byte((i>>16)& 0xff); + send_byte((i>> 8)& 0xff); + send_byte((i>> 0)& 0xff); +} +void send_str(char *s) +{ + if (s == NULL) + send_int(0); + else + { + send_int(strlen(s)+1); + while (*s) + send_byte(*s++); + send_byte(0); + } +} + +static char *sendbuf = NULL; +static int sendsize; +static int sendptr; + +void send_byte(int b) +{ + if (sendptr >= sendsize) + { + sendsize += 100; + sendbuf = (char*)realloc(sendbuf, sendsize); + } + sendbuf[sendptr++] = b; +} + +void init_return() +{ + sendptr = 0; + if (sendbuf == NULL) + { + sendsize = 200; + sendbuf = malloc(sendsize); + } +} + +void do_send(void *con) +{ + set_reply(con, sendbuf, sendptr); + sendbuf = NULL; + sendptr = 0; +} + +char *get_return(int *i) +{ + char *c = sendbuf; + *i = sendptr; + sendbuf = NULL; + sendptr = 0; + return c; +} diff --git a/service.c b/service.c new file mode 100644 index 0000000..4d3a09f --- /dev/null +++ b/service.c @@ -0,0 +1,449 @@ + +#include "metad.h" +#include "skip.h" +#include +#include +#include +#include +#include +#include + +void *services; +void *allprocs; + +int count_procs(service_t sv) +{ + proc_t *pp, p; + int cnt; + time_t now = time(0); + for (cnt=0, pp=skip_first(sv->proc_list) ; pp ; pp=skip_next(pp)) + { + p = *pp; + if (p->exit_time == 0 || p->hold_time > now || p->pipefd >=0 || p->it_forked) + cnt++; + } + return cnt; +} + + +void check_service(service_t sv) +{ + /* check for exited processes and possibly run crash */ + proc_t *pp; + time_t now = time(0); + + for (pp=skip_first(sv->proc_list) ; pp ; ) + { + proc_t p; + p = *pp; + pp = skip_next(pp); + if (p->exit_time == 0) { + /* monitoring a normal child + * we should have been alerted if the child exitted, + * but just-in-case... try sig0 + */ + if (kill(p->pid, 0) == -1 && errno == ESRCH && !is_saved_pid(p->pid)) { + p->status = 0; /* who knows. */ + p->exit_time = now; + } + } + if (p->exit_time > 0 && p->hold_time == 0) + { + /* a newly exited process */ + if (p->is_crash) + { + /* this is a crash recover prog exitting. just set the hold time */ + p->hold_time = p->exit_time + sv->next_hold; + } + else if (WIFSIGNALED(p->status) || (WIFEXITED(p->status) && WEXITSTATUS(p->status) != 0)) + { + /* exited badly */ + if (p->exit_time - p->start_time < 30) { + if (sv->next_hold < MAXINT/2) + sv->next_hold *= 2; + } else + sv->next_hold = 2; + if (0 && sv->crash_prog) + { + /* FIXME */ + } + else p->hold_time = p->exit_time + sv->next_hold; + } + else + { + if (sv->pidfile) + { + /* if this file is newer than start_time and contains a pid, + * record that pid as this pid and shedule regular checks + */ + int fd = open(sv->pidfile, 0); + struct stat stb; + char pidline[100]; + int n; + int pid; + if (fd >= 0 + && fstat(fd, &stb) != -1 + && stb.st_mtime >= p->start_time + && (n=read(fd, pidline, sizeof(pidline)-1))> 0 + && (pidline[n]=0 , pid=atoi(pidline)) > 1 + && kill(pid, 0) != -1 + ) + { + /* looks good ! */ + p->it_forked = pid; + p->hold_time = 1; + waituntil(now+5); /* check it in 5 seconds and periodically */ + } + else + { + /* give the program a few seconds to write to the file + * e.g. xdm forks, then creates the pid file + */ + if (p->exit_time + 10 > now) + { + p->it_forked = -1; + waituntil(p->exit_time + 11); + } + else + { + // give up waiting + p->it_forked = 0; + + /* probably exited badly if it didn't run for long + (keep in mind that the pidfile wasn't updated) */ + if (p->exit_time - p->start_time < 30) { + if (sv->next_hold < MAXINT/2) + sv->next_hold *= 2; + } else + sv->next_hold = 2; + + p->hold_time = p->exit_time + sv->next_hold; + } + } + if (fd >= 0) close(fd); + } else + sv->next_hold = 2; + + } + } + else if (p->exit_time > 0 && p->it_forked > 0) + { + /* monitoring forked child + * try sending sig0 to see if process still exists + */ + if (kill(p->it_forked, 0)== -1 && errno == ESRCH) + { + p->it_forked = 0; + p->exit_time = now; + } + else + waituntil(now+30); /* wait 30 seconds and check again */ + } + if (p->pipefd >= 0) + { + if (readyon(p->pipefd)) + { + int n = read(p->pipefd, p->pipebuf+p->bufptr, sizeof(p->pipebuf)-p->bufptr-1); + if (n<= 0) + { + close(p->pipefd); + p->pipefd = -1; + } + else + { + int i = 0; + p->bufptr += n; + while (i < p->bufptr) + { + while (i < p->bufptr && p->pipebuf[i] != '\n') + i++; + if (p->pipebuf[i] == '\n' || i > sizeof(p->pipebuf)-2) + { + /* ship out this much */ + int j; + p->pipebuf[i] = '\0'; + dolog(sv, p, p->pipebuf); + for (j=i+1 ; jbufptr ; j++) + p->pipebuf[j-i-1] = p->pipebuf[j]; + p-> bufptr -= i+1; + i = 0; + } + else + i++; + } + } + } + if (p->pipefd >= 0) + listenon(p->pipefd); + } + if (p->exit_time > 0 && p->hold_time < now && p->it_forked == 0 + && (p->pipefd == -1 || p->exit_time +30 pipefd>=0) + { + /* it has been 30 seconds since the parent exitted, time to + * discard the pipe... + */ + close(p->pipefd); + p->pipefd = -1; + } + skip_delete(sv->proc_list, &p->pid); + skip_delete(allprocs, &p->pid); + free(p); + } + else if (p->exit_time > 0 && p->hold_time > 2) + waituntil(p->hold_time+1); + } + (sv->class->c_check_service)(sv); +} + +int new_proc(service_t sv, char **env) +{ + /* fork, register new child + * in child, call sv->class->new_child for final setup before exec + */ + int pid; + proc_t p; + char **envp; + extern char **environ; + time_t now; + int pipefd[2]; + + if (sv->enabled == 0) + return -2; + if (sv->max_proc > 0 && count_procs(sv) >= sv->max_proc) + return -2; /* too many already */ + + if (sv->class->prefork(sv)) + return -2; + + now = time(0); + if (sv->watch_output) + { + if (pipe(pipefd)== -1) + pipefd[0] = pipefd[1] = -1; + } + switch (pid = fork()) + { + case -1: + if (sv->watch_output) + { + close(pipefd[0]); close(pipefd[1]); + } + return -1; /* cannot fork */ + case 0: /* child */ + errors_to(ERROR_SYSLOG, NULL); + if (sv->home_dir) + { + if (chdir(sv->home_dir)!=0) + { + error("Couldn't chdir to %s", sv->home_dir); + exit(10); + } + } + if (sv->username) + { + struct passwd *pw = getpwnam(sv->username); + if (pw) { + initgroups(sv->username, pw->pw_gid); + setgid(pw->pw_gid); + setuid(pw->pw_uid); + } else { + error("unknown user %s", sv->username); + exit(10); + } + } + (sv->class->new_child)(sv); + if (!env) + envp = environ; + else + { + int cnt, i; + cnt = 0; + for (i=0 ; env[i] ; i++) cnt++; + for (i=0 ; environ[i] ; i++) cnt++; + envp = (char**)malloc((cnt+1) * sizeof(char*)); + cnt = 0; + for (i=0 ; env[i] ; i++) + envp[cnt++] = env[i]; + for (i=0 ; environ[i] ; i++) + envp[cnt++] = environ[i]; + envp[cnt] = NULL; + } + if (sv->watch_output && pipefd[0] >= 0) + { + close(pipefd[0]); + if (pipefd[1] != 1) close(1); + if (pipefd[1] != 2) close(2); + if (pipefd[1] == 0) + { + pipefd[1] = dup(pipefd[1]); + close(0); + } + open("/dev/null", 0); + dup(pipefd[1]); + if (pipefd[1] > 2) + { + dup(pipefd[1]); + close(pipefd[1]); + } + } + execve(sv->program, sv->args, envp); + exit(11); + default: /* parent */ + p = (proc_t)malloc(sizeof(struct proc)); + if (sv->watch_output) + { + close(pipefd[1]); + p->pipefd = pipefd[0]; + p->bufptr = 0; + if (p->pipefd >= 0) listenon(p->pipefd); + fcntl(p->pipefd, F_SETFD, 1); + } + else + p->pipefd = -1; + p->pid = pid; + p->service = sv; + p->it_forked = 0; + p->is_crash = 0; + p->start_time = now; + p->hold_time = 0; + p->exit_time = 0; + skip_insert(sv->proc_list, p); + sv->start_cnt ++; + skip_insert(allprocs, p); + sv->class->new_parent(sv,p); + } + return pid; +} + +void prepare_restart(void) +{ + service_t *sp; + for (sp = skip_first(services) ; sp ; sp=skip_next(sp)) + { + service_t sv = *sp; + proc_t *pp; + for (pp=skip_first(sv->proc_list) ; pp ; pp=skip_next(pp)) + { + proc_t p = *pp; + if (p->pipefd >= 0) + fcntl(p->pipefd, F_SETFD, 0); + } + } +} + +static int proc_cmp(proc_t a, proc_t b, int *kp) +{ + int k; + if (b) k=b->pid; else k = *kp; + return k - a->pid; +} + +service_t new_service(char *name, class_t class) +{ + service_t sv = (service_t)malloc(sizeof(struct service)); + + sv->service = strdup(name); + sv->class = class; + sv->max_proc = 1; + sv->crash_prog = NULL; + sv->home_dir = NULL; + sv->username = NULL; + sv->pidfile = NULL; + sv->watch_output = 0; + sv->enabled = 1; + sv->start_cnt = 0; + sv->program = NULL; + sv->args = 0; + sv->pending = 0; + sv->proc_list = skip_new(proc_cmp, NULL, NULL); + sv->next_hold = 2; + (*class->copy_state)(NULL, sv); + return sv; +} + +int process_opt(service_t sv, char *opt) +{ + /* max= crash= dir= user= enabled= */ + + if (strncmp(opt, "max=", 4)==0) + { + sv->max_proc = atoi(opt+4); + return 1; + } + else if (strncmp(opt, "crash=", 6)==0) + { + sv->crash_prog = strdup(opt+6); + return 1; + } + else if (strncmp(opt, "dir=", 4)==0) + { + sv->home_dir = strdup(opt+4); + return 1; + } + else if (strncmp(opt, "user=", 5)==0) + { + sv->username = strdup(opt+5); + return 1; + } + else if (strncmp(opt, "enabled=", 8)==0) + { + if (strcmp(opt+8, "no")==0) + sv->enabled = 0; + else if (strcmp(opt+8, "yes")==0) + sv->enabled = 1; + else return -1; + return 1; + } + else if (strncmp(opt, "pidfile=", 8)==0) + { + sv->pidfile = strdup(opt+8); + return 1; + } + else if (strcmp(opt, "watch_output")==0) + { + sv->watch_output = 1; + return 1; + } + else return 0; +} + +static int service_cmp(service_t a, service_t b, char *k) +{ + if (b) k = b->service; + return strcmp(a->service, k); +} + +void service_init(void) +{ + services = skip_new(service_cmp, NULL, NULL); + allprocs = skip_new(proc_cmp, NULL, NULL); +} + +void free_service(service_t sv) +{ + if (sv->service) free(sv->service); + if (sv->crash_prog) free(sv->crash_prog); + if (sv->classinfo) (sv->class->free_state)(sv); + if (sv->home_dir) free(sv->home_dir); + if (sv->username) free(sv->username); + if (sv->program) free(sv->program); + if (sv->pidfile) free(sv->pidfile); + if (sv->args) strlistfree(sv->args); + if (sv->proc_list) skip_free(sv->proc_list); + + free(sv); +} + +service_t find_service(char *name) +{ + service_t *sp; + if (name == NULL) return NULL; + sp = skip_search(services, name); + if (sp) + return *sp; + else + return NULL; +} diff --git a/skip.c b/skip.c new file mode 100644 index 0000000..d7aca1e --- /dev/null +++ b/skip.c @@ -0,0 +1,254 @@ + +/* Skiplists. + Provide three functions to skip_new: + cmp(e1, e2, k) + This should compare e1 with e2 or (if null) k (a key) + free should free an entry + errfn should do something with an error message + + skip_new(cmp,free,errfn) -> list + skip_insert(list,entry) -> bool + skip_delete(list,key) -> bool + skip_search(list,key) -> *entry + skip_first(list) -> *entry + skip_next(*entry) -> *entry + + */ +#include +#include + +#define false 0 +#define true 1 +typedef char boolean; +#define BitsInRandom 31 + +#define MaxNumberOfLevels 16 +#define MaxLevel (MaxNumberOfLevels-1) +#define newNodeOfLevel(l) (node)malloc(sizeof(struct node)+(l)*sizeof(node)) + +typedef void *keyType; +typedef void *valueType; + + +typedef struct node +{ + valueType value; /* must be first for skip_next to work */ + struct node *forward[1]; /* variable sized array of forward pointers */ +} *node; + +typedef struct list +{ + int level; /* Maximum level of the list + (1 more than the number of levels in the list) */ + node header; /* pointer to head of list */ + int (*cmpfn)(); /* compares two values or a key an a value */ + void (*freefn)(); /* free a value */ + void (*errfn)(); /* when malloc error occurs */ +} *list; + +static void default_errfn(char *msg) +{ + write(2, msg, (int)strlen(msg)); + write(2, "\n", 2); + abort(); +} + +static int randomsLeft = 0; +static int randomBits; + +list skip_new(cmpfn, freefn, errfn) +int (*cmpfn)(); +void (*freefn)(); +void (*errfn)(); +{ + list l; + int i; + + if (errfn == NULL) + errfn = default_errfn; + l = (list)malloc(sizeof(struct list)); + if (l == NULL) + { + (*errfn)("Malloc failed in skip_new"); + return NULL; + } + l->level = 0; + l->header = newNodeOfLevel(MaxNumberOfLevels); + if (l->header == NULL) + { + (*errfn)("Malloc failed in skip_new"); + return NULL; + } + for (i=0; iheader->forward[i] = NULL; + l->header->value = NULL; + l->cmpfn = cmpfn; + l->freefn = freefn; + if (errfn) + l->errfn = errfn; + else + l->errfn = default_errfn; + return l; +} + +void skip_free(l) +list l; +{ + register node p; + p = l->header; + while (p != NULL) + { + register node q; + q = p->forward[0]; + if (p != l->header) /* header has no meaningful value */ + (*l->freefn)(p->value); + free(p); + p = q; + } + free(l); +} + +static int randomLevel() +{ + register int level = 0; + register int b; + do { + if (randomsLeft == 0) { +#ifdef POSIX + randomBits = lrand48(); +#else + randomBits = random(); +#endif + randomsLeft = BitsInRandom/2; + } + b = randomBits&3; + randomBits>>=2; + randomsLeft--; + if (!b) level++; + } while (!b); + return(level>MaxLevel ? MaxLevel : level); +} + +boolean skip_insert(l, value) +list l; +valueType value; +{ + int k; + node update[MaxNumberOfLevels]; + node p,q; + int cm; + + p = l->header; + for (k=l->level ; k>=0 ; k--) + { + cm = 1; + while ( p->forward[k] != NULL + && (cm=(*l->cmpfn)(p->forward[k]->value, value, 0))<0) + p = p->forward[k]; + update[k] = p; + } + + if (cm == 0) + return false; + + k = randomLevel(); + if (k> l->level) + { + k = ++l->level; + update[k] = l->header; + } + q = newNodeOfLevel(k); + if (q == NULL) + { + (*l->errfn)("Malloc failed in skip_insert"); + return false; + } + q->value = value; + for ( ; k>=0 ; k--) + { + p = update[k]; + q->forward[k] = p->forward[k]; + p->forward[k] = q; + } + return true; +} + +boolean skip_delete(l, key) +list l; +keyType key; +{ + int k, m; + node update[MaxNumberOfLevels]; + node p,q; + int cm; + + p = l->header; + + for (k=l->level ; k>=0 ; k--) + { + cm = 1; + while ( p->forward[k] != NULL + && (cm=(*l->cmpfn)(p->forward[k]->value, NULL, key))<0) + p = p->forward[k]; + update[k] = p; + } + + if (cm == 0) + { + q = update[0]->forward[0]; + m=l->level; + for (k=0; k<=m && (p=update[k])->forward[k] == q ; k++) + p->forward[k] = q->forward[k]; + if (l->freefn) + (*l->freefn)(q->value); + free(q); + while (l->header->forward[m] == NULL && m > 0) + m--; + l->level = m; + return true; + } + else + return false; +} + +valueType *skip_search(l, key) +list l; +keyType key; +{ + int k; + node p; + int cm; + + p = l->header; + for (k=l->level ; k>=0 ; k--) + { + cm = 1; + while ( p->forward[k] != NULL + && (cm=(*l->cmpfn)(p->forward[k]->value, NULL, key))<0) + p = p->forward[k]; + } + if (cm != 0) + return NULL; + return &p->forward[0]->value; +} + +valueType *skip_first(l) +list l; +{ + node p; + + p = l->header; + if (p->forward[0] == NULL) + return NULL; + return & p->forward[0]->value; +} + +valueType *skip_next(c) +valueType *c; +{ + node p; + p = (node)c; + if (p->forward[0] == NULL) + return NULL; + return & p->forward[0]->value; +} diff --git a/skip.h b/skip.h new file mode 100644 index 0000000..afad69b --- /dev/null +++ b/skip.h @@ -0,0 +1,19 @@ + +/* include for skiplists */ +#define _PROTO_ +#ifdef _PROTO_ +void *skip_new(int cmpfn(), void freefn(), void errfn()); +char skip_insert(void *list, void *entry); +char skip_delete(void *list, void *key); +void *skip_search(void *list, void *key); +void *skip_first(void *list); +void *skip_next(void *last); +void skip_free(void *); +#else +void *skip_new(); +char skip_insert(); +char skip_delete(); +void *skip_search(); +void *skip_first(); +void *skip_next(); +#endif diff --git a/strccmp.c b/strccmp.c new file mode 100644 index 0000000..36b20d4 --- /dev/null +++ b/strccmp.c @@ -0,0 +1,25 @@ + +int strnccmp(a,b, n) +char *a, *b; +int n; +{ + char ac, bc; + + while(n--) + { + ac = *a++; + if (ac>='a' && ac <= 'z') ac -= 'a'-'A'; + bc = *b++; + if (bc>='a' && bc <= 'z') bc -= 'a'-'A'; + if (ac == 0 || ac != bc) break; + } + if (ac bc) return 1; + return 0; +} + +int strccmp(a,b) +char *a, *b; +{ + return strnccmp(a,b,(int)strlen(a)+1); +} diff --git a/strdup.c b/strdup.c new file mode 100644 index 0000000..35bbb06 --- /dev/null +++ b/strdup.c @@ -0,0 +1,22 @@ + +#define NULL (0) +char *malloc(); + +char *strdup(char *s) +{ + if (s==NULL) + return s; + return (char *)strcpy(malloc(strlen(s)+1), s); +} + +char *strndup(char *s, int a) +{ + char *r; + if (s == NULL) + return s; + + r = (char*)malloc(a+1); + strncpy(r, s, a); + r[a] = 0; + return r; +} diff --git a/stream.c b/stream.c new file mode 100644 index 0000000..ae6b818 --- /dev/null +++ b/stream.c @@ -0,0 +1,198 @@ + +#include "metad.h" +#include +#include +#include +#include +#ifdef SOLARIS +#include +#else +#include +#endif + +typedef struct stream_opts +{ + int proto; /* always TCP */ + int port; + int backlog; + int sock; + int newsock; +} *stream_t; + +#define c(sv) ((stream_t)((sv)->classinfo)) + +static int stream_opt(service_t sv, char *opt) +{ + /* port= */ + char *protos = "tcp"; + if (strncmp(opt, "port=", 5) == 0) { + char *ep; + int port; + struct servent *se; + port = strtoul(opt+5, &ep, 10); + if (opt[5] && !*ep) { + c(sv)->port = htons(port); + return 1; + } + se = getservbyname(opt+5, protos); + if (se) { + c(sv)->port = se->s_port; + return 1; + } + } + if (strncmp(opt, "backlog=", 8) == 0) { + char *ep; + c(sv)->backlog = strtoul(opt+8, &ep, 10); + if (opt[8] && !*ep) + return 1; + } + return 0; +} + +static void stream_register(service_t sv) +{ + /* create a socket and bind it */ + stream_t s = sv->classinfo; + struct sockaddr_in addr; + int opt; + + if (s->sock >=0 || s->port <= 0) + return; + s->sock = socket(AF_INET, SOCK_STREAM, s->proto); + if (s->sock < 0) + return; + opt = 1; + setsockopt(s->sock, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = s->port; + if (bind(s->sock, (struct sockaddr *)&addr, sizeof(addr))) { + close(s->sock); + s->sock = -1; + return; + } + + if (listen(s->sock, s->backlog)<0) { + close(s->sock); + s->sock = -1; + return; + } + nodelay(s->sock); +} + +static void stream_unregister(service_t sv) +{ + stream_t s = sv->classinfo; + if (s->sock>=0) + close(s->sock); + s->sock = -1; +} + + +static void stream_check(service_t sv) +{ + stream_t s = sv->classinfo; + + if (s->sock >=0) { + if (readyon(s->sock)) { + char *env[3]; + env[0] = "METAD_REASON=connect"; + env[1] = "METAD_ARG="; + env[2] = NULL; + if (new_proc(sv, env) == -1) + if (s->newsock >= 0) { + close(s->newsock); + s->newsock = -1; + } + } + listenon(s->sock); + } +} + + + +static int stream_prefork(service_t sv) +{ + int f; + stream_t s = sv->classinfo; + if (s->sock < 0) + return -2; + + s->newsock = accept(s->sock, NULL, 0); + if (s->newsock < 0) + return -2; + /* disable NDELAY which might be inherited */ + f = fcntl(s->newsock, F_GETFL, 0); + f &= ~O_NDELAY; + fcntl(s->newsock, F_SETFL, f); + return 0; +} + +static void stream_copy(service_t from, service_t to) +{ + stream_t n,o; + n = (stream_t)malloc(sizeof(struct stream_opts)); + if (from) { + o = from->classinfo; + n->proto = o->proto; + n->port = o->port; + n->sock = o->sock; + n->backlog = o->backlog; + o->sock = -1; + n->newsock = -1; + } else { + n->proto = IPPROTO_TCP; + n->backlog = 10; + n->port = 0; + n->sock = -1; + n->newsock = -1; + } + to->classinfo = n; +} + +static void stream_freestate(service_t sv) +{ + stream_t s = sv->classinfo; + if (s->sock >= 0) + close(s->sock); + free(s); +} + +static void stream_newparent(service_t sv, proc_t p) +{ + if (c(sv)->newsock >= 0) { + close(c(sv)->newsock); + c(sv)->newsock = -1; + } +} + +static void stream_newchild(service_t sv) +{ + close(c(sv)->sock); + if (c(sv)->newsock < 0) + exit(2); + dup2(c(sv)->newsock, 0); + dup2(c(sv)->newsock, 1); + if (c(sv)->newsock > 1) + close(c(sv)->newsock); + +} + +static void stream_send(service_t sv) +{ + send_byte(2); /*stream */ + send_int(c(sv)->proto); + send_int(ntohs(c(sv)->port)); + send_int(c(sv)->backlog); + send_int(c(sv)->sock>=0); +} + +struct class stream_class = +{ "stream", stream_opt, stream_register, stream_check, + stream_copy, stream_freestate, stream_send, + stream_unregister, stream_newparent, stream_newchild, + stream_prefork +}; + + diff --git a/strlistdup.c b/strlistdup.c new file mode 100644 index 0000000..489f943 --- /dev/null +++ b/strlistdup.c @@ -0,0 +1,28 @@ + +/* duplicate a list of strings */ + +#define NULL ((void *)0) +char *strdup(char *); +void *malloc(int); + +char **strlistdup(char **l) +{ + int len = 0; + char **rv; + while (l[len]) len++; + + rv = (char**)malloc((len+1)*sizeof(char *)); + for (len=0 ; l[len]; len++) + rv[len] = strdup(l[len]); + rv[len] = NULL; + return rv; +} + +void strlistfree(char **l) +{ + int i; + for (i=0 ; l[i] ; i++) + free(l[i]); + free(l); +} + diff --git a/strsplit.c b/strsplit.c new file mode 100644 index 0000000..7f82a7c --- /dev/null +++ b/strsplit.c @@ -0,0 +1,79 @@ +#include +#include + +/* + * split a string into an array of strings, + * separated by any chars in the fs string + * strings may contain chars from fs if quoted + * quotes may be escaped or quoted + * + * MWR, UNSW, Oct 84 + */ + +char ** +strsplit(s, fs) +char *s, *fs; +{ + register char *sp, *sp2, *delim, **ssp, *ns; + register unsigned i, num; + static char quote[] = "'"; + + extern char *malloc(); + + if((ns = malloc((unsigned) strlen(s) + 1)) == NULL) + return NULL; + sp = s; + sp2 = ns; + num = 0; + while(*sp) + { + while(*sp && strchr(fs, *sp)) + sp++; + if(!*sp) + break; + + num++; + do + { + delim = fs; + while(*sp && !strchr(delim, *sp)) + { + switch(*sp) + { + case '\'': + case '"': + if(delim == fs) + { + quote[0] = *sp++; + delim = quote; + continue; + } + break; + case '\\': + if(sp[1] == '\'' || sp[1] == '"') + sp++; + break; + } + *sp2++ = *sp++; + } + if(delim == quote && *sp) + sp++; + } + while(delim == quote && *sp); + *sp2++ = '\0'; + } + + if((ssp = (char **) malloc(sizeof(char *) * (num + 2))) == NULL) + return NULL; + sp = ns; + ssp[0] = ns; + for(i = 1; i <= num; i++) + { + ssp[i] = sp; + while(*sp) + sp++; + sp++; + } + ssp[i] = NULL; + return &ssp[1]; +} diff --git a/version.c b/version.c new file mode 100644 index 0000000..c84afa1 --- /dev/null +++ b/version.c @@ -0,0 +1,31 @@ + +char version[] = "2.17"; + +/* + * 2.17 06aug2003 metad: check if pid has benee collected before assuming a failed kill means that it is lost. + * 2.16 18sep2002 metad: add "stream" service for tcp listening + * metac: understand listing of stream service + * 2.15 17aug2001 metad: fixed ordering in main_loop + * 2.14 07aug2001 metad: short malloc in new_proc was causing problems + * 2.13 30apr99 metad: errors during startup were getting lost + * lookup username every time instead of once only + * disassociate from controlling tty + * 2.12 25may98 metad: bugfix - pidfile was not being closed!! + * 2.11 08oct96 metad: bug whereby would sometimes spin on failing process as next_hold was set to 0 + * 2.10 15aug96 metad: that last fix was broken - try again + * 2.09 02aug96 metad: where re-read config file, didn't update server pointers in proc structures... + * 2.08 04jul96 metad: if pidfile and watchout_put options in force, then + * metad would wait for pipe to close even if daemon exitted. + * Now metad only wait 30 more seconds for pipe to close, then closes it. + * Also restart properly saves it_forked info. + * 2.07 02apr96 metad: fix bug in user= processing + * 2.06 06mar96 metad: fix memory buf in free_service + * 2.05 01feb96 metad: preserve enabled state over restart + * 2.04 04dec95 metad: wait a little while for child to write to pidfile -- xdm is slow + * 2.03 14nov95 metad: fixed bug where messaged returned on udb request caused crash + * 2.02 31oct95 metad: pidfile and watch_output options, minor improvements to logging + * metac: understand new protocol phrases for pidfile and watch_output + * 2.01 25oct95 metad: zero holdtimes when run command received + * metac: improve listings: brief and long, hostname included + * + */