From ad77053c4c8dcfe14c33ed2ebc7842c294cbbb32 Mon Sep 17 00:00:00 2001 From: "Neil F. Brown" Date: Tue, 6 Jun 2006 19:26:41 +1000 Subject: [PATCH 1/1] Initial checking --- Buildfile | 55 +++++ PROTOCOL | 27 +++ TODO | 7 + args.c | 119 ++++++++++ args.h | 9 + broadcast.c | 84 ++++++++ classes.c | 17 ++ commands.c | 253 ++++++++++++++++++++++ control.c | 312 +++++++++++++++++++++++++++ daemon.c | 137 ++++++++++++ dlink.c | 74 +++++++ dlink.h | 23 ++ error.c | 112 ++++++++++ loadstate.c | 158 ++++++++++++++ mainloop.c | 158 ++++++++++++++ meta.c | 244 +++++++++++++++++++++ metad.c | 33 +++ metad.h | 145 +++++++++++++ metad.texi | 585 ++++++++++++++++++++++++++++++++++++++++++++++++++ nargs.c | 117 ++++++++++ ports.c | 29 +++ prtime.c | 38 ++++ publish | 16 ++ read_config.c | 148 +++++++++++++ recvlist.c | 37 ++++ sendcmd.c | 324 ++++++++++++++++++++++++++++ sendlist.c | 121 +++++++++++ service.c | 449 ++++++++++++++++++++++++++++++++++++++ skip.c | 254 ++++++++++++++++++++++ skip.h | 19 ++ strccmp.c | 25 +++ strdup.c | 22 ++ stream.c | 198 +++++++++++++++++ strlistdup.c | 28 +++ strsplit.c | 79 +++++++ version.c | 31 +++ 36 files changed, 4487 insertions(+) create mode 100644 Buildfile create mode 100644 PROTOCOL create mode 100644 TODO create mode 100644 args.c create mode 100644 args.h create mode 100644 broadcast.c create mode 100644 classes.c create mode 100644 commands.c create mode 100644 control.c create mode 100644 daemon.c create mode 100644 dlink.c create mode 100644 dlink.h create mode 100644 error.c create mode 100644 loadstate.c create mode 100644 mainloop.c create mode 100644 meta.c create mode 100644 metad.c create mode 100644 metad.h create mode 100644 metad.texi create mode 100644 nargs.c create mode 100644 ports.c create mode 100644 prtime.c create mode 100644 publish create mode 100644 read_config.c create mode 100644 recvlist.c create mode 100644 sendcmd.c create mode 100644 sendlist.c create mode 100644 service.c create mode 100644 skip.c create mode 100644 skip.h create mode 100644 strccmp.c create mode 100644 strdup.c create mode 100644 stream.c create mode 100644 strlistdup.c create mode 100644 strsplit.c create mode 100644 version.c 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 + * + */ -- 2.39.5