#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <dirent.h>
#include "types.h"
#include "distcc.h"
#include "rpc.h"
#include "trace.h"
#include "exitcode.h"
#include "snprintf.h"
#include "mon.h"
#include "util.h"
const int dcc_phase_max_age = 60;
static int dcc_mon_kill_old(int fd,
char *fullpath)
{
struct stat st;
time_t now;
if (fstat(fd, &st) == -1) {
dcc_close(fd);
rs_log_warning("error statting %s: %s", fullpath, strerror(errno));
return EXIT_IO_ERROR;
}
time(&now);
if (now - st.st_mtime > dcc_phase_max_age) {
dcc_close(fd);
rs_trace("unlink %s", fullpath);
if (unlink(fullpath) == -1) {
rs_log_warning("unlink %s failed: %s", fullpath, strerror(errno));
return EXIT_IO_ERROR;
}
return EXIT_GONE;
}
return 0;
}
static int dcc_mon_read_state(int fd, char *fullpath,
struct dcc_task_state *lp)
{
int nread;
nread = read(fd, lp, sizeof *lp);
if (nread == -1) {
rs_trace("failed to read state from %s: %s",
fullpath, strerror(errno));
return EXIT_IO_ERROR;
} else if (nread == 0) {
return EXIT_IO_ERROR;
} else if (nread != sizeof *lp) {
rs_trace("short read getting state from %s",
fullpath);
return EXIT_IO_ERROR;
}
if (lp->magic != DCC_STATE_MAGIC) {
rs_log_warning("wrong magic number: %s",
fullpath);
return EXIT_IO_ERROR;
}
if (lp->struct_size != sizeof (struct dcc_task_state)) {
rs_log_warning("wrong structure size: %s: version mismatch?",
fullpath);
return EXIT_IO_ERROR;
}
lp->file[sizeof lp->file - 1] = '\0';
lp->host[sizeof lp->host - 1] = '\0';
if (lp->curr_phase > DCC_PHASE_DONE) {
lp->curr_phase = DCC_PHASE_COMPILE;
}
lp->next = 0;
return 0;
}
static int dcc_mon_check_orphans(struct dcc_task_state *monl)
{
if (!kill(monl->cpid, 0)) {
return 0;
} else if (errno == EPERM) {
return 0;
} else if (errno == ESRCH) {
return EXIT_GONE;
} else {
rs_log_warning("kill %ld, 0 failed: %s", (long) monl->cpid,
strerror(errno));
return EXIT_GONE;
}
}
static int dcc_mon_load_state(int fd,
char *fullpath,
struct dcc_task_state **ppl)
{
int ret;
struct dcc_task_state *tl;
tl = calloc(1, sizeof *tl);
if (!tl) {
rs_log_crit("failed to allocate dcc_task_state");
return EXIT_OUT_OF_MEMORY;
}
ret = dcc_mon_read_state(fd, fullpath, tl);
if (ret) {
dcc_task_state_free(tl);
*ppl = NULL;
return ret;
}
if (tl->curr_phase != DCC_PHASE_DONE) {
ret = dcc_mon_check_orphans(tl);
if (ret) {
dcc_task_state_free(tl);
*ppl = NULL;
return ret;
}
}
*ppl = tl;
return ret;
}
int dcc_task_state_free(struct dcc_task_state *lp)
{
struct dcc_task_state *next;
while (lp) {
next = lp->next;
free(lp);
lp = next;
}
return 0;
}
static int dcc_mon_do_file(char *dirname, char *filename,
struct dcc_task_state **lp)
{
int fd;
char *fullpath;
int ret;
*lp = NULL;
if (!str_startswith(dcc_state_prefix, filename)) {
return 0;
}
asprintf(&fullpath, "%s/%s", dirname, filename);
rs_trace("process %s", fullpath);
if ((fd = open(fullpath, O_RDONLY|O_BINARY, 0)) == -1) {
if (errno == ENOENT) {
rs_trace("%s disappeared", fullpath);
ret = 0;
goto out_free;
} else {
rs_log_warning("failed to open %s: %s",
fullpath, strerror(errno));
ret = EXIT_IO_ERROR;
goto out_free;
}
}
if ((ret = dcc_mon_kill_old(fd, fullpath))) {
goto out_free;
}
ret = dcc_mon_load_state(fd, fullpath, lp);
dcc_close(fd);
out_free:
free(fullpath);
return ret;
}
static void dcc_mon_insert_sorted(struct dcc_task_state **list,
struct dcc_task_state *new)
{
int s;
struct dcc_task_state *i;
for (; (i = *list) != NULL; list = &i->next) {
s = strcmp(i->host, new->host);
if (s > 0) {
break;
} else if (s == 0) {
if (new->slot < i->slot)
break;
}
}
*list = new;
new->next = i;
}
int dcc_mon_poll(struct dcc_task_state **p_list)
{
int ret;
char *dirname;
DIR *d;
struct dirent *de;
*p_list = NULL;
if ((ret = dcc_get_state_dir(&dirname)))
return ret;
if ((d = opendir(dirname)) == NULL) {
rs_log_error("failed to opendir %s: %s", dirname, strerror(errno));
ret = EXIT_IO_ERROR;
return ret;
}
while ((de = readdir(d)) != NULL) {
struct dcc_task_state *pthis;
if (dcc_mon_do_file(dirname, de->d_name, &pthis) == 0
&& pthis) {
dcc_mon_insert_sorted(p_list, pthis);
}
}
closedir(d);
return 0;
}