Skip to content

Commit

Permalink
feat: setup mailer jobs
Browse files Browse the repository at this point in the history
  • Loading branch information
exbotanical committed Nov 11, 2023
1 parent 20e24bf commit 45acf50
Show file tree
Hide file tree
Showing 20 changed files with 259 additions and 115 deletions.
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
FROM ubuntu:20.04
# TODO: shared
RUN apt-get update && apt-get install -y git gcc make uuid-dev bash curl libpcre3-dev
RUN apt-get update && apt-get install -y git gcc make uuid-dev vim bash curl libpcre3-dev
RUN DEBIAN_FRONTEND=noninteractive apt-get -yq install bsd-mailx
RUN service postfix start

RUN sh -c "`curl -L https://raw.githubusercontent.com/rylnd/shpec/master/install.sh`"

Expand Down
3 changes: 3 additions & 0 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
FROM ubuntu:20.04

RUN apt-get update && apt-get install -y git gcc make uuid-dev vim bash curl libpcre3-dev
RUN DEBIAN_FRONTEND=noninteractive apt-get -yq install bsd-mailx

RUN service postfix start

RUN sh -c "`curl -L https://raw.githubusercontent.com/rylnd/shpec/master/install.sh`"

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ DEPS := $(filter-out $(wildcard $(DEPSDIR)/tap.c/*), $(wildcard $(DEPSDIR)/*/*.c

CFLAGS := -I$(DEPSDIR) -Wall -Wextra -pedantic
UNIT_TEST_CFLAGS := -DUNIT_TEST
LIBS := -lm -lpthread -lpcre
LIBS := -lm -lpthread -lpcre -luuid
# -luuid

TESTS := $(wildcard $(TESTDIR)/*.c)
Expand Down
31 changes: 17 additions & 14 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
# TODOs

- cleanup
- allow user to interpolate a `RELATIVE_DIR` in crontabs (so they can reference files relative to the crontab)
- allow variables in crontab
- schedule and run overdue jobs
- job reporting
- synchronize updates to crontab
- actually read the sys and user crontabs
- perms
- special crontab config support (toml or yaml?)
- log to syslog
- integ testing
- unit tests
- Ensure tests don't log to syslog or whatever (maybe a test sink?)
- handle signals and log getting closed or deleted
- [ ] cleanup
- [ ] allow user to interpolate a `RELATIVE_DIR` in crontabs (so they can reference files relative to the crontab)
- [x] allow variables in crontab
- [ ] schedule and run overdue jobs
- [ ] job reporting
- [x] synchronize updates to crontab
- [ ] actually read the sys ~~and user crontabs~~
- [ ] perms
- [ ] special crontab config support (toml or yaml?)
- [x] log to syslog
- [ ] syslog levels?
- [x] integ testing
- [x] unit tests
- [ ] Ensure tests don't log to syslog or whatever (maybe a test sink?)
- [ ] handle signals and log getting closed or deleted
- [ ] daemon lock
- [ ] Allow log feature flags (fs logs, job logs, time logs, etc)
8 changes: 4 additions & 4 deletions deps/libhash/hash_table.c
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,11 @@ hash_table *ht_init(int base_capacity) {
}

void ht_insert(hash_table *ht, const char *key, void *value) {
return __ht_insert(ht, key, value, false);
__ht_insert(ht, key, value, false);
}

void ht_insert_ptr(hash_table *ht, const char *key, void *value) {
return __ht_insert(ht, key, value, true);
__ht_insert(ht, key, value, true);
}

ht_record *ht_search(hash_table *ht, const char *key) {
Expand All @@ -241,9 +241,9 @@ char *ht_get(hash_table *ht, const char *key) {
return r ? r->value : NULL;
}

void ht_delete_table(hash_table *ht) { return __ht_delete_table(ht, false); }
void ht_delete_table(hash_table *ht) { __ht_delete_table(ht, false); }

void ht_delete_table_ptr(hash_table *ht) { return __ht_delete_table(ht, true); }
void ht_delete_table_ptr(hash_table *ht) { __ht_delete_table(ht, true); }

int ht_delete(hash_table *ht, const char *key) {
return __ht_delete(ht, key, false);
Expand Down
24 changes: 24 additions & 0 deletions scripts/prime.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash

ROOT_DIR="$(dirname "$(readlink -f $BASH_SOURCE)")"
SETUP_PATH="$ROOT_DIR/../t/integ/utils/setup.bash"

. "$SETUP_PATH"
uname_1='narcissus'
uname_2='demian'
uname_3='isao'

setup_user_crondir

setup_user $uname_1
setup_user $uname_2

setup_user_crontab $uname_3

add_user_job $uname_1 1
add_user_job $uname_2 1
add_user_job $uname_3 1

add_user_job $uname_1 3
add_user_job $uname_1 5
add_user_job $uname_2 2
2 changes: 1 addition & 1 deletion src/cli.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include "cli.h"

#include "commander/commander.h"
#include "defs.h"
#include "constants.h"

static void setopt_logfile(command_t* self) {
opts.log_file = (char*)self->arg;
Expand Down
31 changes: 15 additions & 16 deletions src/defs.h → src/constants.h
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
#ifndef DEFS_H
#define DEFS_H
#ifndef CONSTANTS_H
#define CONSTANTS_H

#include <fcntl.h>

#include "libhash/libhash.h"
#include "libutil/libutil.h"

#ifndef SYS_CRONTABS_DIR
#define SYS_CRONTABS_DIR "/etc/cron.d"
#endif

#ifndef CRONTABS_DIR
#define CRONTABS_DIR "/var/spool/cron/crontabs"
#endif

#ifndef DEFAULT_PATH
#define DEFAULT_PATH "/usr/bin:/bin:/usr/sbin:/sbin"
#endif

#define ROOT_UNAME "root"
#define ROOT_UID 0

#define ALL_PERMS 07777
#define OWNER_RW_PERMS 0600

#define HOMEDIR_ENVVAR "HOME"
#define SHELL_ENVVAR "SHELL"
#define PATH_ENVVAR "PATH"
#define UNAME_ENVVAR "USER"
#define MAILTO_ENVVAR "MAILTO"

#define SMALL_BUFFER 256
#define MED_BUFFER SMALL_BUFFER * 4
#define LARGE_BUFFER MED_BUFFER * 2

#define MAILCMD_FMT "%s -r%s@%s -s 'job: %s' %s"

#define CHRONIC_VERSION "0.0.1"
#define DAEMON_IDENT "crond"

extern pid_t daemon_pid;
extern const char *job_status_names[];
extern char hostname[SMALL_BUFFER];

extern array_t *job_queue;

#endif /* DEFS_H */
extern const char *job_status_names[];
#endif /* CONSTANTS_H */
6 changes: 4 additions & 2 deletions src/crontab.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#include "constants.h"
#include "cronentry.h"
#include "defs.h"
#include "log.h"
#include "opt-constants.h"
#include "parser.h"
#include "util.h"

Expand Down Expand Up @@ -189,7 +191,7 @@ Crontab* new_crontab(int crontab_fd, bool is_root, time_t curr_time,
FILE* fd;
if (!(fd = fdopen(crontab_fd, "r"))) {
printlogf("fdopen on crontab_fd %d failed\n", crontab_fd);
// TODO: perrors
perror("fdopen");
return NULL;
}

Expand Down
133 changes: 112 additions & 21 deletions src/job.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,73 +4,164 @@
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <uuid/uuid.h>

#include "constants.h"
#include "cronentry.h"
#include "defs.h"
#include "libutil/libutil.h"
#include "log.h"
#include "opt-constants.h"
#include "util.h"

// need to free
static char* create_uuid(void) {
char uuid[UUID_STR_LEN];

uuid_t bin_uuid;
uuid_generate_random(bin_uuid);
uuid_unparse(bin_uuid, uuid);

return s_copy(uuid);
}

// TODO: pass entry by value? Otherwise if entry is freed before we call fork,
// we're fucked
static Job* new_job(CronEntry* entry) {
Job* job = xmalloc(sizeof(Job));
job->ret = -1;
job->pid = -1;
job->mailer_pid = -1;
job->status = PENDING;
job->cmd = s_copy(entry->cmd); // TODO: free
job->ident = create_uuid();

ht_record* r = ht_search(entry->parent->vars, MAILTO_ENVVAR);
job->mailto = s_copy(r ? r->value : entry->parent->uname);

return job;
}

static bool await_job(pid_t pid, int* status) {
int r = waitpid(pid, status, WNOHANG);

printlogf("[pid=%d] waitpid result is %d\n", pid, r);

// -1 == error; 0 == still running; pid == dead
if (r < 0 || r == pid) {
if (r > 0 && WIFEXITED(*status)) {
*status = WEXITSTATUS(*status);
} else {
*status = 1;
}
return true;
}

return false;
}

void enqueue_job(CronEntry* entry) {
Job* job = new_job(entry);

char* home = ht_get(entry->parent->vars, HOMEDIR_ENVVAR);
char* shell = ht_get(entry->parent->vars, SHELL_ENVVAR);

if ((job->pid = fork()) == 0) {
// Detach from the crond and become session leader
setsid();

dup2(STDERR_FILENO, STDOUT_FILENO);

printlogf(
"Writing log from child process pid=%d homedir=%s shell=%s cmd=%s\n",
getpid(), home, shell, job->cmd);
"[job %s] Writing log from child process pid=%d homedir=%s shell=%s "
"cmd=%s\n",
job->ident, getpid(), home, shell, job->cmd);

chdir(home);

int r = execle(shell, shell, "-c", job->cmd, NULL, entry->parent->envp);
printlogf("execle finished with %d\n", r);

printlogf("execle failed with %d\n", r);
perror("execle");
_exit(1);
_exit(EXIT_FAILURE);
}

printlogf("New running job with pid %d\n", job->pid);
printlogf("[job %s] New running job with pid %d\n", job->ident, job->pid);

job->status = RUNNING;
array_push(job_queue, job);
}

unsigned int temporary_mail_count = 0;
// TODO: [bash] <defunct>
// execl("/bin/sh", "/bin/sh", "-c", job->cmd, NULL);
// ps aux | grep './chronic' | grep -v 'grep' | awk '{print $2}' | wc -l
// ps aux | grep './chronic' | grep -v 'grep' | awk '{print $2}' | xargs kill
void run_mailjob(Job* mailjob) {
mailjob->status = MAIL_RUNNING;
printlogf("[job %s] transition EXITED->MAIL_RUNNING\n", mailjob->ident);

char mail_cmd[MED_BUFFER];

sprintf(mail_cmd, MAILCMD_FMT, MAILCMD_PATH, DAEMON_IDENT, hostname,
mailjob->cmd, mailjob->mailto);

printlogf("going to run mailcmd: %s\n", mail_cmd);

if ((mailjob->mailer_pid = fork()) == 0) {
setsid();
dup2(STDERR_FILENO, STDOUT_FILENO);

FILE* mail_pipe = popen(mail_cmd, "w");

if (mail_pipe == NULL) {
perror("popen");
exit(EXIT_FAILURE);
}

fprintf(mail_pipe, fmt_str("This is the body of the email (num %d).\n",
++temporary_mail_count));

if (pclose(mail_pipe) == -1) {
perror("pclose");
exit(EXIT_FAILURE);
}
}
}
// ps aux | grep './chronic' | awk '{print $2}' | xargs kill
void reap_job(Job* job) {
// This shouldn't happen, but just in case...
if (job->pid == -1 || job->status == EXITED) {
if (job->status == RESOLVED) {
printlogf(
"[job %s] Tried to reap job with status=%s. This is a "
"bug.\n",
job->ident, job_status_names[job->status]);
return;
}

int status;
int r = waitpid(job->pid, &status, WNOHANG);

printlogf("Job with pid=%d waitpid result is %d\n", job->pid, r);
// -1 == error; 0 == still running; pid == dead
if (r < 0 || r == job->pid) {
if (r > 0 && WIFEXITED(status)) {
status = WEXITSTATUS(status);
} else {
status = 1;
}
if (job->status == MAIL_RUNNING) {
printlogf("[job %s] await (pid=%d, status=MAIL_RUNNING)\n", job->ident,
job->mailer_pid);

printlogf("set job with pid=%d to EXITED (status=%d)\n", job->pid, status);
// Main job is done, await the mail job
if (await_job(job->mailer_pid, &status)) {
printlogf(
"[job %s] transition MAIL_RUNNING->RESOLVED (pid=%d, status=%d)\n",
job->ident, job->mailer_pid, status);

job->ret = status;
job->status = EXITED;
job->pid = -1;
// TODO: cleanup fn
job->status = RESOLVED;
job->mailer_pid = -1;
// TODO: cleanup fns
}
} else {
if (await_job(job->pid, &status)) {
printlogf("[job %s] transition RUNNING->EXITED (pid=%d, status=%d)\n",
job->ident, job->pid, status);

job->ret = status;
job->status = EXITED;
job->pid = -1;
}
}
}
Loading

0 comments on commit 45acf50

Please sign in to comment.