Job Control

Job and process tracking for foreground/background execution.

A job is one pipeline launched by the shell (foreground or background). Each job has its own process group (pgid) so the controlling terminal can deliver signals to the whole pipeline at once, and so the shell can suspend or resume it independently of itself.

Author

pulgamecanica

  • t_shell.jobs : t_list* of t_job* (one entry per known job)

  • t_job.processes : t_list* of t_process* (one entry per pipeline stage)

Enums

enum t_job_status

Lifecycle state of a job, recomputed from its processes.

Values:

enumerator JOB_RUNNING

At least one process is still running and none stopped.

enumerator JOB_STOPPED

At least one process received SIGTSTP/SIGSTOP (Ctrl-Z).

enumerator JOB_DONE

Every process exited normally (any exit code).

enumerator JOB_TERMINATED

At least one process was killed by a signal.

Functions

int job_control_init(struct s_shell *shell)

Place the shell in its own process group and claim the terminal.

Idempotent when the shell is already a session leader. Skipped when shell->interactive is 0 (job control is a no-op without a controlling TTY). On return shell->shell_pgid is authoritative and shell->original_termios holds the saved terminal settings.

Returns:

0 on success, 1 on setpgid failure.

t_job *job_create(struct s_shell *shell, const char *cmd_line)

Allocate a job, append it to shell->jobs, and make it current_job.

The next free id is max(existing ids) + 1. cmd_line is duplicated with ft_strdup and released by job_free.

Returns:

Newly created job, or NULL on allocation failure.

void job_add_process(t_job *job, pid_t pid, const char *cmd)

Register a forked child as a stage of job.

Appends a t_process to job->processes. cmd is duplicated.

t_job *job_find_by_id(struct s_shell *shell, int id)

Find a job by its shell-assigned id, or NULL.

t_job *job_find_by_pid(struct s_shell *shell, pid_t pid)

Find the job containing a given pid, or NULL.

t_job *job_find_by_spec(struct s_shell *shell, const char *spec)

Resolve a bash-style job spec to a t_job*.

Accepted forms: NULL / “” / “%” / “%+” / “%%” → current job; “%-” → the job preceding the current one; “%N” → id N; “%prefix” → first job whose cmd_line starts with prefix.

Returns:

Matching job or NULL if spec is malformed / no such job.

void job_free(void *job_ptr)

Free a job and all of its processes.

Matches the void (*)(void *) deleter signature of ft_lstdel.

void job_remove(struct s_shell *shell, t_job *job)

Unlink job from shell->jobs and release it.

Clears shell->current_job if it referenced the removed job. No-op if job is not in the list (silent for safety).

int job_launch_background(struct s_shell *shell, t_job *job)

Hand a job off to the background and print [id] pgid on stderr.

The child was already forked with its own pgid by the caller. This does not wait - the job is reaped later by job_update_statuses between prompts.

Returns:

0 on success, 1 if job is NULL.

int job_launch_foreground(struct s_shell *shell, t_job *job)

Hand the terminal to job, wait for it, then take it back.

Uses tcsetpgrp(terminal_fd, job->pgid) to transfer terminal ownership so SIGINT/SIGTSTP reach the pipeline. After job_wait returns, the shell reclaims the terminal and restores its saved termios. Non-interactive shells skip the tc* handoff and just reap the pgid.

Returns:

Exit status of the pipeline (last completed process), or 128+WSTOPSIG if the job was stopped.

int job_continue_foreground(struct s_shell *shell, t_job *job)

Resume a stopped job in the foreground.

Sends SIGCONT to the job’s pgid, hands the terminal back to it, and waits. Leaves the job in shell->jobs if it stops again, removes it on completion.

Returns:

Exit status, or 128+WSTOPSIG if stopped again.

int job_continue_background(struct s_shell *shell, t_job *job)

Resume a stopped job in the background.

Sends SIGCONT and prints [id] cmd_line & on stderr. Does not wait - the job is reaped between prompts like any bg job.

Returns:

0 on success, 1 on invalid input or kill failure.

int job_wait(struct s_shell *shell, t_job *job)

Blocking reap of every process in job.

Loops waitpid(-pgid, ..., WUNTRACED) until every process is completed or at least one is stopped. Per-process flags and the aggregate status are updated in place.

Returns:

Exit status of the last completed process, 128+WSTOPSIG on stop, -1 on unrecoverable waitpid error.

int job_apply_status(t_job *job, pid_t pid, int status)

Apply a single waitpid result to one process of job.

Updates status, completed, stopped, clears notified, and flips the aggregate status to JOB_TERMINATED on a signalled death. Does not recompute running/stopped - call job_recompute_status afterwards.

Returns:

1 if pid belonged to this job, 0 otherwise.

void job_recompute_status(t_job *job)

Recompute job->status from the flags of its processes.

Result is JOB_DONE (all completed, no signalled), JOB_TERMINATED (preserved if set by job_apply_status), JOB_STOPPED (any stopped), or JOB_RUNNING. Idempotent.

void job_update_statuses(struct s_shell *shell)

Non-blocking reap of every shell-owned child.

Loops waitpid(-1, ..., WNOHANG | WUNTRACED | WCONTINUED) until there is nothing more to collect. Updates per-process flags and recomputes the owning job’s status. Safe to call when no jobs are tracked.

void job_notify(struct s_shell *shell)

Print a status line for every job that has not been notified yet, then drop JOB_DONE / JOB_TERMINATED jobs from shell->jobs.

Call between prompts. shell->current_job is cleared if it referenced a job that gets removed.

const char *job_status_str(t_job_status s)

Human-readable label for a t_job_status value (for listings).

void job_print_line(int fd, struct s_shell *shell, t_job *job)

Print one job listing line, bash-compatible.

Format: “[N]M Status[(SIG)]<pad> cmd-line\n”, where M is the current-job marker (‘+’, ‘-’, or ‘ ‘) and (SIG) is appended only for non-SIGTSTP stops or signal-terminated jobs.

Parameters:
  • fd – File descriptor to write to (STDOUT for jobs, STDERR for auto-notifications).

void job_print_termination(t_job *job)

Print “Segmentation fault (core dumped)”-style message for a foreground job whose last process died by signal.

Mirrors bash’s foreground signal-death output. Silent for SIGINT and SIGPIPE (bash suppresses those to avoid noise from Ctrl-C and broken-pipe situations). Adds “ (core dumped)” when WCOREDUMP indicates a core file was produced.

void job_control_cleanup(struct s_shell *shell)

Release every job and process still tracked by shell.

Called from shell_cleanup on exit. Leaves shell->jobs NULL and shell->current_job NULL.

struct t_process
#include <job_control.h>

A single process belonging to a job (one pipeline stage).

Stored in t_job.processes as the content of a t_list node. Reaped and updated by job_update_statuses.

Public Members

pid_t pid

Child pid returned by fork().

char *cmd

Human-readable description of this stage (for listings).

int status

Raw wstatus value from the last waitpid() on this pid.

int completed

1 once the process has exited (normally or by signal).

int stopped

1 while the process is stopped (SIGTSTP/SIGSTOP).

struct t_job
#include <job_control.h>

One pipeline launched by the shell (background or foreground).

Stored in t_shell.jobs as the content of a t_list node. Built by job_create, grown by job_add_process, reaped by job_update_statuses, and released by job_free (either via job_notify when completed, or job_control_cleanup on exit).

Public Members

int id

Shell-assigned sequential id (1-based); used by N job-spec and by jobs listings.

pid_t pgid

Process-group id shared by every pipeline stage.

char *cmd_line

Displayed by jobs/notifications. Built from the AST subtree via ast_to_string, not the raw input line, so ls ; sleep 5 & shows only sleep 5.

t_list *processes

t_list* of t_process* - one per pipeline stage.

t_job_status status

Aggregate lifecycle state (see t_job_status).

int notified

1 once the user has been informed about the current status; cleared whenever status changes so the next job_notify prints it again.

int foreground

1 if the job currently owns the controlling terminal.