Sync OpenBSD patchset 855:

Simplify the way jobs work and drop the persist type, so all jobs are
fire-and-forget.

Status jobs now managed with two trees of output (new and old), rather
than storing the output in the jobs themselves. When the status line is
processed any jobs which don't appear in the new tree are started and
the output from the old tree displayed. When a job finishes it updates
the new tree with its output and that is used for any subsequent
redraws. When the status interval expires, the new tree is moved to the
old so that all jobs are run again.

This fixes the "#(echo %H:%M:%S)" problem which would lead to thousands
of identical persistent jobs and high memory use (this can still be
achieved by adding "sleep 30" but that is much less likely to happen by
accident).
pull/1/head
Tiago Cunha 2011-02-15 15:20:03 +00:00
parent 3b56ebce6d
commit 3d7b8105e1
8 changed files with 201 additions and 230 deletions

View File

@ -1,4 +1,4 @@
/* $Id: cmd-if-shell.c,v 1.11 2011-01-07 14:45:34 tcunha Exp $ */ /* $Id: cmd-if-shell.c,v 1.12 2011-02-15 15:20:03 tcunha Exp $ */
/* /*
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org> * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
@ -53,7 +53,7 @@ cmd_if_shell_exec(struct cmd *self, struct cmd_ctx *ctx)
{ {
struct args *args = self->args; struct args *args = self->args;
struct cmd_if_shell_data *cdata; struct cmd_if_shell_data *cdata;
struct job *job; const char *shellcmd = args->argv[0];
cdata = xmalloc(sizeof *cdata); cdata = xmalloc(sizeof *cdata);
cdata->cmd = xstrdup(args->argv[1]); cdata->cmd = xstrdup(args->argv[1]);
@ -64,9 +64,7 @@ cmd_if_shell_exec(struct cmd *self, struct cmd_ctx *ctx)
if (ctx->curclient != NULL) if (ctx->curclient != NULL)
ctx->curclient->references++; ctx->curclient->references++;
job = job_add(NULL, 0, NULL, job_run(shellcmd, cmd_if_shell_callback, cmd_if_shell_free, cdata);
args->argv[0], cmd_if_shell_callback, cmd_if_shell_free, cdata);
job_run(job);
return (1); /* don't let client exit */ return (1); /* don't let client exit */
} }

View File

@ -1,4 +1,4 @@
/* $Id: cmd-run-shell.c,v 1.10 2011-01-07 14:45:34 tcunha Exp $ */ /* $Id: cmd-run-shell.c,v 1.11 2011-02-15 15:20:03 tcunha Exp $ */
/* /*
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org> * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
@ -53,7 +53,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmd_ctx *ctx)
{ {
struct args *args = self->args; struct args *args = self->args;
struct cmd_run_shell_data *cdata; struct cmd_run_shell_data *cdata;
struct job *job; const char *shellcmd = args->argv[0];
cdata = xmalloc(sizeof *cdata); cdata = xmalloc(sizeof *cdata);
cdata->cmd = xstrdup(args->argv[0]); cdata->cmd = xstrdup(args->argv[0]);
@ -64,9 +64,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmd_ctx *ctx)
if (ctx->curclient != NULL) if (ctx->curclient != NULL)
ctx->curclient->references++; ctx->curclient->references++;
job = job_add(NULL, 0, NULL, job_run(shellcmd, cmd_run_shell_callback, cmd_run_shell_free, cdata);
args->argv[0], cmd_run_shell_callback, cmd_run_shell_free, cdata);
job_run(job);
return (1); /* don't let client exit */ return (1); /* don't let client exit */
} }

View File

@ -1,4 +1,4 @@
/* $Id: cmd-server-info.c,v 1.43 2011-02-15 15:12:28 tcunha Exp $ */ /* $Id: cmd-server-info.c,v 1.44 2011-02-15 15:20:03 tcunha Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicm@users.sourceforge.net> * Copyright (c) 2008 Nicholas Marriott <nicm@users.sourceforge.net>
@ -175,8 +175,8 @@ cmd_server_info_exec(unused struct cmd *self, struct cmd_ctx *ctx)
ctx->print(ctx, "Jobs:"); ctx->print(ctx, "Jobs:");
LIST_FOREACH(job, &all_jobs, lentry) { LIST_FOREACH(job, &all_jobs, lentry) {
ctx->print(ctx, "%s [fd=%d, pid=%d, status=%d, flags=0x%x]", ctx->print(ctx, "%s [fd=%d, pid=%d, status=%d]",
job->cmd, job->fd, job->pid, job->status, job->flags); job->cmd, job->fd, job->pid, job->status);
} }
return (0); return (0);

View File

@ -1,4 +1,4 @@
/* $Id: cmd-set-option.c,v 1.107 2011-01-07 15:02:38 tcunha Exp $ */ /* $Id: cmd-set-option.c,v 1.108 2011-02-15 15:20:03 tcunha Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net> * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@ -87,11 +87,8 @@ cmd_set_option_exec(struct cmd *self, struct cmd_ctx *ctx)
struct winlink *wl; struct winlink *wl;
struct client *c; struct client *c;
struct options *oo; struct options *oo;
struct jobs *jobs;
struct job *job, *nextjob;
const char *optstr, *valstr; const char *optstr, *valstr;
u_int i; u_int i;
int try_again;
/* Work out the options tree and table to use. */ /* Work out the options tree and table to use. */
if (args_has(self->args, 's')) { if (args_has(self->args, 's')) {
@ -181,23 +178,7 @@ cmd_set_option_exec(struct cmd *self, struct cmd_ctx *ctx)
strcmp(oe->name, "window-status-format") == 0) { strcmp(oe->name, "window-status-format") == 0) {
for (i = 0; i < ARRAY_LENGTH(&clients); i++) { for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
c = ARRAY_ITEM(&clients, i); c = ARRAY_ITEM(&clients, i);
if (c == NULL || c->session == NULL) if (c != NULL && c->session != NULL)
continue;
jobs = &c->status_jobs;
do {
try_again = 0;
job = RB_ROOT(jobs);
while (job != NULL) {
nextjob = RB_NEXT(jobs, jobs, job);
if (job->flags & JOB_PERSIST) {
job_remove(jobs, job);
try_again = 1;
break;
}
job = nextjob;
}
} while (try_again);
server_redraw_client(c); server_redraw_client(c);
} }
} }

185
job.c
View File

@ -1,4 +1,4 @@
/* $Id: job.c,v 1.22 2011-02-15 15:12:28 tcunha Exp $ */ /* $Id: job.c,v 1.23 2011-02-15 15:20:03 tcunha Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicm@users.sourceforge.net> * Copyright (c) 2009 Nicholas Marriott <nicm@users.sourceforge.net>
@ -30,129 +30,32 @@
* output. * output.
*/ */
void job_callback(struct bufferevent *, short, void *);
/* All jobs list. */ /* All jobs list. */
struct joblist all_jobs = LIST_HEAD_INITIALIZER(all_jobs); struct joblist all_jobs = LIST_HEAD_INITIALIZER(all_jobs);
RB_GENERATE(jobs, job, entry, job_cmp); /* Start a job running, if it isn't already. */
void job_callback(struct bufferevent *, short, void *);
int
job_cmp(struct job *job1, struct job *job2)
{
return (strcmp(job1->cmd, job2->cmd));
}
/* Initialise job tree. */
void
job_tree_init(struct jobs *jobs)
{
RB_INIT(jobs);
}
/* Destroy a job tree. */
void
job_tree_free(struct jobs *jobs)
{
struct job *job;
while (!RB_EMPTY(jobs)) {
job = RB_ROOT(jobs);
RB_REMOVE(jobs, jobs, job);
job_free(job);
}
}
/* Find a job and return it. */
struct job * struct job *
job_get(struct jobs *jobs, const char *cmd) job_run(const char *cmd,
{
struct job job;
job.cmd = (char *) cmd;
return (RB_FIND(jobs, jobs, &job));
}
/* Add a job. */
struct job *
job_add(struct jobs *jobs, int flags, struct client *c, const char *cmd,
void (*callbackfn)(struct job *), void (*freefn)(void *), void *data) void (*callbackfn)(struct job *), void (*freefn)(void *), void *data)
{ {
struct job *job; struct job *job;
job = xmalloc(sizeof *job);
job->cmd = xstrdup(cmd);
job->pid = -1;
job->status = 0;
job->client = c;
job->fd = -1;
job->event = NULL;
job->callbackfn = callbackfn;
job->freefn = freefn;
job->data = data;
job->flags = flags;
if (jobs != NULL)
RB_INSERT(jobs, jobs, job);
LIST_INSERT_HEAD(&all_jobs, job, lentry);
return (job);
}
/* Remove job from tree and free. */
void
job_remove(struct jobs *jobs, struct job *job)
{
if (jobs != NULL)
RB_REMOVE(jobs, jobs, job);
job_free(job);
}
/* Kill and free an individual job. */
void
job_free(struct job *job)
{
job_kill(job);
LIST_REMOVE(job, lentry);
xfree(job->cmd);
if (job->freefn != NULL && job->data != NULL)
job->freefn(job->data);
if (job->fd != -1)
close(job->fd);
if (job->event != NULL)
bufferevent_free(job->event);
xfree(job);
}
/* Start a job running, if it isn't already. */
int
job_run(struct job *job)
{
struct environ env; struct environ env;
pid_t pid;
int nullfd, out[2]; int nullfd, out[2];
if (job->fd != -1 || job->pid != -1)
return (0);
if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0) if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0)
return (-1); return (NULL);
environ_init(&env); environ_init(&env);
environ_copy(&global_environ, &env); environ_copy(&global_environ, &env);
server_fill_environ(NULL, &env); server_fill_environ(NULL, &env);
switch (job->pid = fork()) { switch (pid = fork()) {
case -1: case -1:
environ_free(&env); environ_free(&env);
return (-1); return (NULL);
case 0: /* child */ case 0: /* child */
clear_signals(1); clear_signals(1);
@ -177,23 +80,55 @@ job_run(struct job *job)
closefrom(STDERR_FILENO + 1); closefrom(STDERR_FILENO + 1);
execl(_PATH_BSHELL, "sh", "-c", job->cmd, (char *) NULL); execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL);
fatal("execl failed"); fatal("execl failed");
default: /* parent */ }
/* parent */
environ_free(&env); environ_free(&env);
close(out[1]); close(out[1]);
job = xmalloc(sizeof *job);
job->cmd = xstrdup(cmd);
job->pid = pid;
job->status = 0;
LIST_INSERT_HEAD(&all_jobs, job, lentry);
job->callbackfn = callbackfn;
job->freefn = freefn;
job->data = data;
job->fd = out[0]; job->fd = out[0];
setblocking(job->fd, 0); setblocking(job->fd, 0);
if (job->event != NULL) job->event = bufferevent_new(job->fd, NULL, NULL, job_callback, job);
bufferevent_free(job->event);
job->event =
bufferevent_new(job->fd, NULL, NULL, job_callback, job);
bufferevent_enable(job->event, EV_READ); bufferevent_enable(job->event, EV_READ);
return (0); log_debug("run job %p: %s, pid %ld", job, job->cmd, (long) job->pid);
return (job);
} }
/* Kill and free an individual job. */
void
job_free(struct job *job)
{
log_debug("free job %p: %s", job, job->cmd);
LIST_REMOVE(job, lentry);
xfree(job->cmd);
if (job->freefn != NULL && job->data != NULL)
job->freefn(job->data);
if (job->pid != -1)
kill(job->pid, SIGTERM);
if (job->fd != -1)
close(job->fd);
if (job->event != NULL)
bufferevent_free(job->event);
xfree(job);
} }
/* Job buffer error callback. */ /* Job buffer error callback. */
@ -203,15 +138,16 @@ job_callback(unused struct bufferevent *bufev, unused short events, void *data)
{ {
struct job *job = data; struct job *job = data;
bufferevent_disable(job->event, EV_READ); log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid);
close(job->fd);
job->fd = -1;
if (job->pid == -1) { if (job->pid == -1) {
if (job->callbackfn != NULL) if (job->callbackfn != NULL)
job->callbackfn(job); job->callbackfn(job);
if ((!job->flags & JOB_PERSIST))
job_free(job); job_free(job);
} else {
bufferevent_disable(job->event, EV_READ);
close(job->fd);
job->fd = -1;
} }
} }
@ -219,23 +155,14 @@ job_callback(unused struct bufferevent *bufev, unused short events, void *data)
void void
job_died(struct job *job, int status) job_died(struct job *job, int status)
{ {
log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid);
job->status = status; job->status = status;
job->pid = -1;
if (job->fd == -1) { if (job->fd == -1) {
if (job->callbackfn != NULL) if (job->callbackfn != NULL)
job->callbackfn(job); job->callbackfn(job);
if ((!job->flags & JOB_PERSIST))
job_free(job); job_free(job);
} } else
}
/* Kill a job. */
void
job_kill(struct job *job)
{
if (job->pid == -1)
return;
kill(job->pid, SIGTERM);
job->pid = -1; job->pid = -1;
} }

View File

@ -1,4 +1,4 @@
/* $Id: server-client.c,v 1.53 2011-01-21 23:56:53 tcunha Exp $ */ /* $Id: server-client.c,v 1.54 2011-02-15 15:20:03 tcunha Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicm@users.sourceforge.net> * Copyright (c) 2009 Nicholas Marriott <nicm@users.sourceforge.net>
@ -78,7 +78,8 @@ server_client_create(int fd)
c->tty.sy = 24; c->tty.sy = 24;
screen_init(&c->status, c->tty.sx, 1, 0); screen_init(&c->status, c->tty.sx, 1, 0);
job_tree_init(&c->status_jobs); RB_INIT(&c->status_new);
RB_INIT(&c->status_old);
c->message_string = NULL; c->message_string = NULL;
ARRAY_INIT(&c->message_log); ARRAY_INIT(&c->message_log);
@ -138,8 +139,9 @@ server_client_lost(struct client *c)
if (c->stderr_event != NULL) if (c->stderr_event != NULL)
bufferevent_free(c->stderr_event); bufferevent_free(c->stderr_event);
status_free_jobs(&c->status_new);
status_free_jobs(&c->status_old);
screen_free(&c->status); screen_free(&c->status);
job_tree_free(&c->status_jobs);
if (c->title != NULL) if (c->title != NULL)
xfree(c->title); xfree(c->title);
@ -220,7 +222,6 @@ server_client_status_timer(void)
{ {
struct client *c; struct client *c;
struct session *s; struct session *s;
struct job *job;
struct timeval tv; struct timeval tv;
u_int i; u_int i;
int interval; int interval;
@ -249,8 +250,7 @@ server_client_status_timer(void)
difference = tv.tv_sec - c->status_timer.tv_sec; difference = tv.tv_sec - c->status_timer.tv_sec;
if (difference >= interval) { if (difference >= interval) {
RB_FOREACH(job, jobs, &c->status_jobs) status_update_jobs(c);
job_run(job);
c->flags |= CLIENT_STATUS; c->flags |= CLIENT_STATUS;
} }
} }

116
status.c
View File

@ -1,4 +1,4 @@
/* $Id: status.c,v 1.155 2011-01-07 16:55:40 tcunha Exp $ */ /* $Id: status.c,v 1.156 2011-02-15 15:20:03 tcunha Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net> * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@ -33,7 +33,8 @@ char *status_redraw_get_left(
struct client *, time_t, int, struct grid_cell *, size_t *); struct client *, time_t, int, struct grid_cell *, size_t *);
char *status_redraw_get_right( char *status_redraw_get_right(
struct client *, time_t, int, struct grid_cell *, size_t *); struct client *, time_t, int, struct grid_cell *, size_t *);
char *status_job(struct client *, char **); char *status_find_job(struct client *, char **);
void status_job_free(void *);
void status_job_callback(struct job *); void status_job_callback(struct job *);
char *status_print( char *status_print(
struct client *, struct winlink *, time_t, struct grid_cell *); struct client *, struct winlink *, time_t, struct grid_cell *);
@ -49,6 +50,16 @@ char *status_prompt_complete(const char *);
/* Status prompt history. */ /* Status prompt history. */
ARRAY_DECL(, char *) status_prompt_history = ARRAY_INITIALIZER; ARRAY_DECL(, char *) status_prompt_history = ARRAY_INITIALIZER;
/* Status output tree. */
RB_GENERATE(status_out_tree, status_out, entry, status_out_cmp);
/* Output tree comparison function. */
int
status_out_cmp(struct status_out *so1, struct status_out *so2)
{
return (strcmp(so1->cmd, so2->cmd));
}
/* Retrieve options for left string. */ /* Retrieve options for left string. */
char * char *
status_redraw_get_left(struct client *c, status_redraw_get_left(struct client *c,
@ -365,9 +376,8 @@ status_replace1(struct client *c,struct winlink *wl,
ch = ')'; ch = ')';
goto skip_to; goto skip_to;
} }
if ((ptr = status_job(c, iptr)) == NULL) if ((ptr = status_find_job(c, iptr)) == NULL)
return; return;
freeptr = ptr;
goto do_replace; goto do_replace;
case 'H': case 'H':
if (gethostname(tmp, sizeof tmp) != 0) if (gethostname(tmp, sizeof tmp) != 0)
@ -469,9 +479,9 @@ status_replace(struct client *c,
/* Figure out job name and get its result, starting it off if necessary. */ /* Figure out job name and get its result, starting it off if necessary. */
char * char *
status_job(struct client *c, char **iptr) status_find_job(struct client *c, char **iptr)
{ {
struct job *job; struct status_out *so, so_find;
char *cmd; char *cmd;
int lastesc; int lastesc;
size_t len; size_t len;
@ -504,25 +514,89 @@ status_job(struct client *c, char **iptr)
(*iptr)++; /* skip final ) */ (*iptr)++; /* skip final ) */
cmd[len] = '\0'; cmd[len] = '\0';
job = job_get(&c->status_jobs, cmd); /* First try in the new tree. */
if (job == NULL) { so_find.cmd = cmd;
job = job_add(&c->status_jobs, so = RB_FIND(status_out_tree, &c->status_new, &so_find);
JOB_PERSIST, c, cmd, status_job_callback, xfree, NULL); if (so != NULL && so->out != NULL)
job_run(job); return (so->out);
/* If not found at all, start the job and add to the tree. */
if (so == NULL) {
job_run(cmd, status_job_callback, status_job_free, c);
c->references++;
so = xmalloc(sizeof *so);
so->cmd = xstrdup(cmd);
so->out = NULL;
RB_INSERT(status_out_tree, &c->status_new, so);
} }
/* Lookup in the old tree. */
so_find.cmd = cmd;
so = RB_FIND(status_out_tree, &c->status_old, &so_find);
xfree(cmd); xfree(cmd);
if (job->data == NULL) if (so != NULL)
return (xstrdup("")); return (so->out);
return (xstrdup(job->data)); return (NULL);
}
/* Free job tree. */
void
status_free_jobs(struct status_out_tree *sotree)
{
struct status_out *so, *so_next;
so_next = RB_MIN(status_out_tree, sotree);
while (so_next != NULL) {
so = so_next;
so_next = RB_NEXT(status_out_tree, sotree, so);
RB_REMOVE(status_out_tree, sotree, so);
if (so->out != NULL)
xfree(so->out);
xfree(so->cmd);
xfree(so);
}
}
/* Update jobs on status interval. */
void
status_update_jobs(struct client *c)
{
/* Free the old tree. */
status_free_jobs(&c->status_old);
/* Move the new to old. */
memcpy(&c->status_old, &c->status_new, sizeof c->status_old);
RB_INIT(&c->status_new);
}
/* Free status job. */
void
status_job_free(void *data)
{
struct client *c = data;
c->references--;
} }
/* Job has finished: save its result. */ /* Job has finished: save its result. */
void void
status_job_callback(struct job *job) status_job_callback(struct job *job)
{ {
struct client *c = job->data;
struct status_out *so, so_find;
char *line, *buf; char *line, *buf;
size_t len; size_t len;
if (c->flags & CLIENT_DEAD)
return;
so_find.cmd = job->cmd;
so = RB_FIND(status_out_tree, &c->status_new, &so_find);
if (so == NULL || so->out != NULL)
return;
buf = NULL; buf = NULL;
if ((line = evbuffer_readline(job->event->input)) == NULL) { if ((line = evbuffer_readline(job->event->input)) == NULL) {
len = EVBUFFER_LENGTH(job->event->input); len = EVBUFFER_LENGTH(job->event->input);
@ -530,17 +604,11 @@ status_job_callback(struct job *job)
if (len != 0) if (len != 0)
memcpy(buf, EVBUFFER_DATA(job->event->input), len); memcpy(buf, EVBUFFER_DATA(job->event->input), len);
buf[len] = '\0'; buf[len] = '\0';
} } else
buf = xstrdup(line);
if (job->data != NULL) so->out = buf;
xfree(job->data); server_redraw_client(c);
else
server_redraw_client(job->client);
if (line == NULL)
job->data = buf;
else
job->data = xstrdup(line);
} }
/* Return winlink status line entry and adjust gc as necessary. */ /* Return winlink status line entry and adjust gc as necessary. */

35
tmux.h
View File

@ -1,4 +1,4 @@
/* $Id: tmux.h,v 1.609 2011-02-15 15:12:28 tcunha Exp $ */ /* $Id: tmux.h,v 1.610 2011-02-15 15:20:03 tcunha Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net> * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@ -667,8 +667,6 @@ struct job {
pid_t pid; pid_t pid;
int status; int status;
struct client *client;
int fd; int fd;
struct bufferevent *event; struct bufferevent *event;
@ -676,13 +674,8 @@ struct job {
void (*freefn)(void *); void (*freefn)(void *);
void *data; void *data;
int flags;
#define JOB_PERSIST 0x1 /* don't free after callback */
RB_ENTRY(job) entry;
LIST_ENTRY(job) lentry; LIST_ENTRY(job) lentry;
}; };
RB_HEAD(jobs, job);
LIST_HEAD(joblist, job); LIST_HEAD(joblist, job);
/* Screen selection. */ /* Screen selection. */
@ -1087,6 +1080,15 @@ struct message_entry {
time_t msg_time; time_t msg_time;
}; };
/* Status output data from a job. */
struct status_out {
char *cmd;
char *out;
RB_ENTRY(status_out) entry;
};
RB_HEAD(status_out_tree, status_out);
/* Client connection. */ /* Client connection. */
struct client { struct client {
struct imsgbuf ibuf; struct imsgbuf ibuf;
@ -1116,8 +1118,9 @@ struct client {
struct event repeat_timer; struct event repeat_timer;
struct status_out_tree status_old;
struct status_out_tree status_new;
struct timeval status_timer; struct timeval status_timer;
struct jobs status_jobs;
struct screen status; struct screen status;
#define CLIENT_TERMINAL 0x1 #define CLIENT_TERMINAL 0x1
@ -1359,18 +1362,10 @@ const char *options_table_print_entry(
/* job.c */ /* job.c */
extern struct joblist all_jobs; extern struct joblist all_jobs;
int job_cmp(struct job *, struct job *); struct job *job_run(
RB_PROTOTYPE(jobs, job, entry, job_cmp);
void job_tree_init(struct jobs *);
void job_tree_free(struct jobs *);
struct job *job_get(struct jobs *, const char *);
struct job *job_add(struct jobs *, int, struct client *,
const char *, void (*)(struct job *), void (*)(void *), void *); const char *, void (*)(struct job *), void (*)(void *), void *);
void job_remove(struct jobs *, struct job *);
void job_free(struct job *); void job_free(struct job *);
int job_run(struct job *);
void job_died(struct job *, int); void job_died(struct job *, int);
void job_kill(struct job *);
/* environ.c */ /* environ.c */
int environ_cmp(struct environ_entry *, struct environ_entry *); int environ_cmp(struct environ_entry *, struct environ_entry *);
@ -1656,6 +1651,10 @@ void server_clear_identify(struct client *);
void server_update_event(struct client *); void server_update_event(struct client *);
/* status.c */ /* status.c */
int status_out_cmp(struct status_out *, struct status_out *);
RB_PROTOTYPE(status_out_tree, status_out, entry, status_out_cmp);
void status_free_jobs(struct status_out_tree *);
void status_update_jobs(struct client *);
int status_redraw(struct client *); int status_redraw(struct client *);
char *status_replace( char *status_replace(
struct client *, struct winlink *, const char *, time_t, int); struct client *, struct winlink *, const char *, time_t, int);