Finish job control

This commit is contained in:
savalet
2025-05-21 22:24:26 +02:00
parent 91014a8f42
commit d24143b3f5
15 changed files with 135 additions and 53 deletions

View File

@@ -1,6 +1,5 @@
# TODO
- [ ] Add job control in main branch
- [ ] Refactor all the unstable code
- [x] Add job control in main branch
- [ ] Add auto complete
- [ ] Finish line editing
- [ ] Syntax highlighting

View File

@@ -80,6 +80,10 @@ bool process_args(ast_t *node, args_t *args, size_t *toks_i, ef_t *ef)
tok.str[tok.sz] = '\0';
return (process_globbing(tok.str, args, toks_i));
}
if (tok.type == T_JOB) {
ef->bg = true;
return true;
}
if (tok.type == T_TILDE)
return handle_tilde(ef, &tok, args);
handle_var_case(node, ef->exec_ctx, toks_i, args);

View File

@@ -40,9 +40,10 @@ typedef enum size_t {
T_BACKSLASH = 1 << 19,
T_NEWLINE = 1 << 20, // \n
T_TILDE = 1 << 21, // ~
T_EOF = 1 << 22, // \0
T_ARG = 1 << 23,
T_INVALID = 1 << 24
T_JOB = 1 << 22, // &
T_EOF = 1 << 23, // \0
T_ARG = 1 << 24,
T_INVALID = 1 << 25
} token_type_t;
typedef enum {

View File

@@ -39,7 +39,7 @@ ast_t *parse_arg(ast_ctx_t *ctx, ast_t *node)
ctx->act_tok.type = T_ARG;
}
if (ctx->act_tok.type & (T_ARG | T_REDIRECT | T_APPEND |
T_IN_REDIRECT | T_HEREDOC | T_VAR | T_STAR | T_TILDE)) {
T_IN_REDIRECT | T_HEREDOC | T_VAR | T_STAR | T_TILDE | T_JOB)) {
if (!ensure_node_cap(node))
return nullptr;
node->vector.tokens[node->vector.sz] = ctx->act_tok;

View File

@@ -17,6 +17,7 @@ const tokens_list_t TOKENS_LIST[] = {
{ T_QUOTES, "\'", 1, "T_QUOTES"},
{ T_DQUOTES, "\"", 1, "T_DQUOTES"},
{ T_AND, "&&", 2, "T_AND" },
{ T_JOB, "&", 1, "T_JOB" },
{ T_OR, "||", 2, "T_OR" },
{ T_PIPE, "|", 1, "T_PIPE" },
{ T_BACKTICK, "`", 1, "T_BACKTICK" },

View File

@@ -57,4 +57,5 @@ int builtins_astprint(ef_t *, char **args);
int builtins_termname(ef_t *ef, char **);
int builtins_fg(ef_t *ef, char **);
int builtins_bg(ef_t *ef, char **);
int builtins_jobs(ef_t *ef, char **args);
#endif /* BUILTIND_H */

View File

@@ -8,20 +8,30 @@
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include "common.h"
#include "exec.h"
int builtins_bg(ef_t *ef, char **)
int builtins_bg(ef_t *ef, char **args)
{
int last_job_index = ef->exec_ctx->jobs.sz - 1;
size_t job_idx = ef->exec_ctx->jobs.sz - 1;
char *end_ptr;
if (!ef->exec_ctx->isatty) {
fprintf(stderr, "No job control in this shell.\n");
return RETURN_FAILURE;
}
kill(-ef->exec_ctx->jobs.jobs[last_job_index].pgid, SIGCONT);
if (args[1] != nullptr) {
job_idx = strtol(args[1], &end_ptr, 10);
if (*end_ptr != '\0' || !job_idx
|| job_idx > ef->exec_ctx->jobs.sz - 1)
return fprintf(stderr, "%s: No such job.\n", args[0]),
RETURN_FAILURE;
}
ef->exec_ctx->jobs.jobs[job_idx].running = true;
kill(-ef->exec_ctx->jobs.jobs[job_idx].pgid, SIGCONT);
return RETURN_SUCCESS;
}

View File

@@ -4,27 +4,45 @@
** File description:
** _
*/
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include "common.h"
#include "exec.h"
#include "job.h"
int builtins_fg(ef_t *ef, char **)
static
bool get_job_idx(ef_t *ef, char **args, size_t *job_idx)
{
int status = 0;
int last_job_index = ef->exec_ctx->jobs.sz - 1;
char *end_ptr;
if (args[1] != nullptr) {
*job_idx = strtol(args[1], &end_ptr, 10);
if (*end_ptr != '\0' || !*job_idx
|| *job_idx > ef->exec_ctx->jobs.sz - 1)
return fprintf(stderr, "%s: No such job.\n", args[0]), false;
}
return true;
}
int builtins_fg(ef_t *ef, char **args)
{
size_t job_idx = ef->exec_ctx->jobs.sz - 1;
int status;
if (!ef->exec_ctx->isatty) {
fprintf(stderr, "No job control in this shell.\n");
return RETURN_FAILURE;
}
if (!set_child_term(ef->exec_ctx, last_job_index))
if (!get_job_idx(ef, args, &job_idx))
return RETURN_FAILURE;
kill(-ef->exec_ctx->jobs.jobs[last_job_index].pgid, SIGCONT);
waitpid(-ef->exec_ctx->jobs.jobs[last_job_index].pgid, &status, WUNTRACED);
if (!set_child_term(ef->exec_ctx, job_idx))
return RETURN_FAILURE;
kill(-ef->exec_ctx->jobs.jobs[job_idx].pgid, SIGCONT);
waitpid(-ef->exec_ctx->jobs.jobs[job_idx].pgid, &status, WUNTRACED);
if (WIFEXITED(status)) {
ef->exec_ctx->history->last_exit_code =
ef->exec_ctx->history->last_exit_code ?:

23
src/builtins/jobs.c Normal file
View File

@@ -0,0 +1,23 @@
/*
** EPITECH PROJECT, 2025
** __
** File description:
** _
*/
#include <stdio.h>
#include <unistd.h>
#include "common.h"
#include "exec.h"
int builtins_jobs(ef_t *ef, char **)
{
if (!ef->exec_ctx->isatty)
return RETURN_SUCCESS;
for (size_t i = 1; i < ef->exec_ctx->jobs.sz; i++)
dprintf(ef->out_fd, "[%lu] + %s\t %s.\n", i,
ef->exec_ctx->jobs.jobs[i].running ? "Running" : "Suspended",
ef->exec_ctx->jobs.jobs[i].bin_name);
return RETURN_SUCCESS;
}

View File

@@ -20,6 +20,7 @@ typedef struct {
pid_t pgid;
bool running;
bool foreground;
char *bin_name;
} job_t;
typedef struct {

View File

@@ -55,7 +55,8 @@ const builtins_funcs_t BUILTINS[] = {
{ "termname", &builtins_termname },
{ "echo", &builtins_echo },
{ "fg", &builtins_fg },
{ "bg", &builtins_bg }
{ "bg", &builtins_bg },
{ "jobs", &builtins_jobs }
};
const size_t BUILTINS_SZ = sizeof BUILTINS / sizeof *BUILTINS;
@@ -136,35 +137,40 @@ void set_fd(ef_t *ef)
}
static
int exec(char *full_bin_path, char **args, ef_t *ef)
int wait_child(ef_t *ef, int status, pid_t pid)
{
execve(full_bin_path, args, ef->env->env);
return command_error(full_bin_path, args, errno);
bool untraced = (!(ef->flags & F_PIPE) || ef->p_i == ef->p_sz - 1);
if (!init_child_job(ef->exec_ctx, pid, ef))
return RETURN_FAILURE;
if (ef->bg) {
printf("[%lu] %d\n", ef->exec_ctx->jobs.sz, pid);
return RETURN_SUCCESS;
}
waitpid(pid, &status, untraced ? WUNTRACED : WNOHANG);
if (WIFSTOPPED(status)) {
ef->exec_ctx->jobs.jobs[ef->exec_ctx->jobs.sz - 1].running = false;
ef->exec_ctx->jobs.jobs[ef->exec_ctx->jobs.sz - 1].foreground = false;
printf("\nSuspended\n");
}
return status;
}
static
int launch_bin(char *full_bin_path, char **args, ef_t *ef)
{
int status;
int status = 0;
pid_t pid = fork();
bool untraced = (!(ef->flags & F_PIPE) || ef->p_i == ef->p_sz - 1);
if (pid == 0) {
set_fd(ef);
restore_term_flags(ef->exec_ctx);
init_child_job(ef->exec_ctx, pid);
status = exec(full_bin_path, args, ef);
init_child_job(ef->exec_ctx, pid, ef);
execve(full_bin_path, args, ef->env->env);
status = command_error(full_bin_path, args, errno);
exit(RETURN_FAILURE);
}
if (!init_child_job(ef->exec_ctx, pid))
return RETURN_FAILURE;
waitpid(pid, &status, untraced ? WUNTRACED : WNOHANG);
if (WIFSTOPPED(status)) {
ef->exec_ctx->jobs.jobs[ef->exec_ctx->jobs.sz - 1].running = true;
ef->exec_ctx->jobs.jobs[ef->exec_ctx->jobs.sz - 1].foreground = false;
printf("\nSuspended\n");
}
return status;
return wait_child(ef, status, pid);
}
static

View File

@@ -44,6 +44,7 @@ typedef struct {
int in_fd;
int out_fd;
exec_ctx_t *exec_ctx;
bool bg;
} ef_t;
__attribute__((nonnull))

View File

@@ -9,6 +9,7 @@
#include <unistd.h>
#include "builtins_handler.h"
#include "exec.h"
#include "job.h"
void set_ignored_signals(int child)
@@ -36,32 +37,39 @@ bool ensure_jobs_capacity(jobs_t *jobs)
bool set_child_term(exec_ctx_t *ec, size_t idx)
{
if (tcsetpgrp(ec->read_fd, ec->jobs.jobs[idx].pgid) < 0)
return tcsetpgrp(ec->read_fd, ec->jobs.jobs[idx].pgid) < 0 ? false : true;
}
static
bool init_child_parent(exec_ctx_t *ec, pid_t pid, ef_t *ef)
{
if (!ef->bg) {
setpgid(pid, pid);
tcsetpgrp(ec->read_fd, pid);
}
if (!ensure_jobs_capacity(&ec->jobs))
return false;
ec->jobs.jobs[ec->jobs.sz].pgid = pid;
ec->jobs.jobs[ec->jobs.sz].running = true;
ec->jobs.jobs[ec->jobs.sz].foreground = !ef->bg;
ec->jobs.jobs[ec->jobs.sz].bin_name =
ec->history_command[ec->history_command->sz - 1].command;
ec->jobs.sz++;
return true;
}
bool init_child_job(exec_ctx_t *ec, pid_t pid)
bool init_child_job(exec_ctx_t *ec, pid_t pid, ef_t *ef)
{
if (!ec->isatty)
return true;
if (pid == 0) {
setpgid(0, 0);
if (tcsetpgrp(ec->read_fd, getpid()) < 0)
if (!ef->bg && tcsetpgrp(ec->read_fd, getpid()) < 0)
return false;
set_ignored_signals(1);
} else {
setpgid(pid, pid);
if (tcsetpgrp(ec->read_fd, pid) < 0)
return false;
if (!ensure_jobs_capacity(&ec->jobs))
return false;
ec->jobs.jobs[ec->jobs.sz].pgid = pid;
ec->jobs.jobs[ec->jobs.sz].running = true;
ec->jobs.jobs[ec->jobs.sz].foreground = true;
ec->jobs.sz++;
return true;
}
return true;
return init_child_parent(ec, pid, ef);
}
bool init_jobs(exec_ctx_t *ec)

View File

@@ -10,11 +10,12 @@
#include <sys/wait.h>
#include "builtins_handler.h"
#include "exec.h"
#define DEFAULT_JOBS_CAP 4
bool ensure_jobs_capacity(jobs_t *jobs);
bool init_jobs(exec_ctx_t *ec);
bool init_child_job(exec_ctx_t *ec, pid_t pid);
bool init_child_job(exec_ctx_t *ec, pid_t pid, ef_t *ef);
bool set_child_term(exec_ctx_t *ec, size_t idx);
void set_ignored_signals(int child);
#endif /* JOB_H */

View File

@@ -241,10 +241,8 @@ TESTS = [
cmds=[
"expr 1 + 2",
"expr 4 - 2",
#"expr 3 \\* 5",
#"expr 5 = 5",
#"expr 5 \\< 10",
#"expr 5 \\> 3",
"expr 5 = 5",
"expr 1 != 5",
],
depends_on=("ARGS",)
),
@@ -307,7 +305,6 @@ TESTS = [
"where cd\n",
"where echo\n",
"alias ll ls\nwhere ll\n",
#"alias ll ls\nwhich ll\n",
],
depends_on=("ALIAS",)
),
@@ -342,4 +339,15 @@ TESTS = [
],
depends_on=()
),
Test(
key="JOBS_ERROR",
name="jobs error",
cmds=[
"fg\n",
"bg\n",
"jobs\n",
],
depends_on=()
),
]