Skip to content
Snippets Groups Projects
Commit b97d2bbb authored by Joe Pater's avatar Joe Pater
Browse files

Init

parents
No related branches found
No related tags found
No related merge requests found
LDFLAGS += -lreadline
CFLAGS += -g
bp: command.o io.o
cc $(LDFLAGS) -o bp $^
command.o: shell.h utils.h io.h
io.o: io.h shell.h utils.h
command.c 0 → 100644
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include "io.h"
#include "utils.h"
#include "shell.h"
struct proc *get_unused_proc(struct job *job)
{
return &job->procs[job->num_procs++];
}
int make_proc(struct proc *pr, const char *cmd, const char **argv,
struct job *job)
{
pr->state = PROC_INITIALISED;
pr->cmd = strdup(cmd);
for (pr->argc = 0; argv[pr->argc]; pr->argc++)
;
pr->argv = calloc(pr->argc+1, sizeof(char *));
for (int i = 0; i < pr->argc; i++) {
pr->argv[i] = strdup(argv[i]);
}
pr->job = job;
return 0;
}
int connect_proc_fd(struct proc *p, int fd_in_proc, int curr_fd,
int close_on_run)
{
if (fd_in_proc >= MAX_REDIRECTED_FDS) {
pr_err("fd too high");
return -1;
}
struct redir rd = {
.used = 1,
.close = close_on_run,
.fd = curr_fd,
};
p->fds[fd_in_proc] = rd;
return 0;
}
int connect_proc_proc(struct proc *proc1, int fd1,
struct proc *proc2, int fd2)
{
int pipe_fds[2];
if (pipe(pipe_fds)) {
pr_err("Pipe failed");
return -1;
}
return connect_proc_fd(proc1, fd1, pipe_fds[1], 1) ||
connect_proc_fd(proc2, fd2, pipe_fds[0], 1);
}
int connect_proc_file(struct proc *pr, int fd, const char *filename,
int write, int append)
{
int file_fd = open(filename, (write ? O_WRONLY : O_RDONLY) |
(append ? O_APPEND : 0) |
O_CREAT | O_NOCTTY | O_CLOEXEC, 0640);
return connect_proc_fd(pr, fd, file_fd, 1);
}
int connect_proc_defaults(struct proc *pr)
{
for (int i=0; i <= 2; i++) {
if (connect_proc_fd(pr, i, i, 0)) {
return -1;
}
}
}
static int dup_to_unused(struct job *job, int fd)
{
if (dup2(fd, job->unused_fd_num++)) {
pr_err("dup2 failed");
return -1;
}
if (fcntl(fd, F_SETFD, O_CLOEXEC)) {
pr_err("fcntl failed");
return -1;
}
return 0;
}
static int dup_fds(struct proc *pr)
{
for (int fd = 0; fd < MAX_REDIRECTED_FDS; fd++) {
/* If fd is to be used */
if (pr->fds[fd].used) {
int fd_to = pr->fds[fd].fd; /* fd to dup to */
/* If fd to dup to > fd and it is in use, dup that to unused fd */
if (fd_to > fd && pr->fds[fd_to].used) {
int ret = dup_to_unused(pr->job, fd_to);
//pr_dbg("Proc %s duping %d to %d", pr->cmd, fd_to, ret);
if (ret < 0) {
pr_err("dup2 failed");
return -1;
} else { /* Change the fd_to's fd to the new fd */
pr->fds[fd_to].fd = ret;
}
}
//pr_dbg("Proc %s duping %d to %d", pr->cmd, fd, fd_to);
if (dup2(fd_to, fd) < 0) {
pr_err("dup2 failed");
return -1;
}
}
}
}
static void close_proc_fds(struct proc *pr)
{
for (int i=0; i < MAX_REDIRECTED_FDS; i++) {
if (pr->fds[i].close) {
close(pr->fds[i].fd);
}
}
}
static int start_proc(struct proc *pr)
{
int is_pgrp_leader = pr->job->pgrp == PGRP_UNINITIALISED;
pid_t ret = fork();
if (ret < 0) {
pr_err("Fork error");
pr->state = PROC_ERROR;
return -1;
} else if (ret == 0 ) { /* Child */
if (dup_fds(pr)) {
exit(EXIT_FAILURE);
}
if (is_pgrp_leader) { /* If group leader, setpgrp */
setpgrp();
}
if (execvp(pr->cmd, pr->argv)) {
pr_err("exec failed");
exit(EXIT_FAILURE);
}
} else { /* Parent */
close_proc_fds(pr);
if (is_pgrp_leader) { /* If child is group leader, set pgrp */
pr->job->pgrp = ret;
}
pr->state = PROC_RUNNING;
pr->pid = ret;
return 0;
}
return -1; /* Unreachable */
}
int init_job(struct job *job)
{
job->state = JOB_INITIALISED;
memset(job->procs, 0, sizeof(struct proc) * JOB_MAX_PROC);
job->num_procs = 0;
job->finished_procs = 0;
job->unused_fd_num = MAX_REDIRECTED_FDS;
job->pgrp = PGRP_UNINITIALISED;
job->is_bg = 0;
return 0;
}
struct job *fg_job = NULL;
int start_job(struct job *job)
{
if (!job->is_bg) {
fg_job = job;
}
for (int i=0; i < job->num_procs; i++) {
start_proc(&job->procs[i]);
}
return 0;
}
struct job alljobs[MAX_JOBS];
struct job *get_free_job(void)
{
for (int i=0; i < MAX_JOBS; i++) {
if (alljobs[i].state == JOB_UNALLOCATED) {
init_job(&alljobs[i]);
return &alljobs[i];
}
}
return NULL;
}
void free_job(struct job *j)
{
for (int i=0; i < j->num_procs; i++) {
free(j->procs[i].cmd);
for (char **s=j->procs[i].argv; *s; s++) {
free(*s);
}
free(j->procs[i].argv);
}
j->state = JOB_UNALLOCATED;
}
struct proc *lookup_proc_pid(pid_t pid)
{
for (int i=0; i < MAX_JOBS; i++) {
struct job *job = &alljobs[i];
for (int j=0; j < job->num_procs; j++) {
struct proc *p = &job->procs[j];
if (p->pid == pid) {
return p;
}
}
}
return NULL;
}
int check_jobs(void)
{
int statloc;
pid_t pid;
while (1) {
/* Check for any child process */
pid = waitpid(-1, &statloc, fg_job ? 0 : WNOHANG);
if (pid == 0) {
return 0;
} else if (pid == -1) {
return -1;
}
pid_event(pid, statloc);
}
}
static volatile int lastexit = 0;
void pid_event(pid_t pid, int statloc)
{
struct proc *pr = lookup_proc_pid(pid);
if (pr == NULL) {
pr_err("PID not found");
return;
}
pr->wstatus = statloc;
struct job *job = pr->job;
if (job->num_procs == ++job->finished_procs) {
if (job->is_bg) {
register_pre_prompt("Background job finished\n");
} else {
fg_job = NULL;
}
free_job(job);
}
}
#if 0
const char *args1[] = {
"cat",
"/tmp/file",
NULL
};
const char *args2[] = {
"grep",
"a",
NULL
};
int main(void)
{
struct job *j = get_free_job();
struct proc *pr1 = get_unused_proc(j);
make_proc(pr1, "cat", args1, j);
struct proc *pr2 = get_unused_proc(j);
make_proc(pr2, "grep", args2, j);
if (
connect_proc_proc(pr1, STDOUT_FILENO,
pr2, STDIN_FILENO))
pr_err("B");
if (start_job(j)) {
pr_err("A");
}
while (1) {
check_jobs();
sleep(1);
}
return 0;
}
#endif
io.c 0 → 100644
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <readline/readline.h>
#include <readline/history.h>
#include "shell.h"
#include "utils.h"
#include "io.h"
#define PR_BUF_LEN 1024
char pre_prompt_buf[PR_BUF_LEN];
void register_pre_prompt(const char *str)
{
int len = strlen(pre_prompt_buf);
char *s = pre_prompt_buf + len;
if (len > PR_BUF_LEN - 1) return;
strncpy(s, str, PR_BUF_LEN - len - 2);
}
void flush_pre_prompt(FILE *stream)
{
fprintf(stream, pre_prompt_buf);
pre_prompt_buf[0] = '\0';
}
static int is_special(char c)
{
switch (c) {
case '&':
case '|':
case '<':
case '>':
return 1;
default:
return 0;
}
}
/* Return array of tokens, terminated by TOK_NONE. Must be freed */
static struct token *tokenise(const char *str)
{
const char *c = str;
int tokbuflen = strlen(str) + 10; /* plenty! */
struct token *tokbuf = calloc(tokbuflen, sizeof(struct token));
char *tmpbuf = malloc(strlen(str) + 1);
int tmpbuf_ind = 0;
int tok_ind = 0;
char quote = '\0';
int escape = 0;
for (; *c; c++) {
if (quote) {
if (*c == quote) quote = '\0';
else tmpbuf[tmpbuf_ind++] = *c;
} else if (escape) {
if (*c != '\n') {
tmpbuf[tmpbuf_ind++] = *c;
}
} else {
switch (*c) {
case '\'':
case '"':
quote = *c;
break;
case ' ':
case '\t':
case '&':
case '|':
case '>':
case '<':
if (tmpbuf_ind != 0) {
tmpbuf[tmpbuf_ind] = '\0';
tokbuf[tok_ind].str = strdup(tmpbuf);
tokbuf[tok_ind++].type = TOK_STR;
tmpbuf_ind = 0;
}
if (is_special(*c)) {
tokbuf[tok_ind].type = TOK_SPECIAL;
tokbuf[tok_ind++].special = *c;
}
break;
default:
tmpbuf[tmpbuf_ind++] = *c;
break;
}
}
}
if (tmpbuf_ind != 0) {
tmpbuf[tmpbuf_ind] = '\0';
tokbuf[tok_ind].str = strdup(tmpbuf);
tokbuf[tok_ind++].type = TOK_STR;
tmpbuf_ind = 0;
}
tokbuf[tok_ind].type = TOK_NONE;
free(tmpbuf);
return tokbuf;
}
void free_token(struct token *buf)
{
for (int i=0; buf[i].type != TOK_NONE; i++) {
if (buf[i].type == TOK_STR) {
free(buf[i].str);
}
}
free(buf);
}
enum {
STATE_
};
struct proc *end_command(struct job *job, struct proc *p, struct proc **pipe_prev,
const char **tmpargs, int *arg_ind, int pipe_terminated)
{
if (*arg_ind == 0) return 0;
struct proc *pr = get_unused_proc(job);
if (*pipe_prev) {
connect_proc_proc(*pipe_prev, STDOUT_FILENO,
pr, STDIN_FILENO);
}
make_proc(pr, tmpargs[0], tmpargs, job);
if (pipe_terminated) *pipe_prev = pr;
*arg_ind = 0;
return pr;
}
int end_job(struct job **job, int amp_term)
{
if ((*job)->num_procs == 0) {
free_job(*job); /* Job unused */
return 0;
}
(*job)->is_bg = amp_term;
start_job(*job);
*job = get_free_job();
}
int process_line(const char *str)
{
struct token *tokbuf = tokenise(str);
struct token *tokstart = tokbuf;
const char **tmpargs = calloc(strlen(str), sizeof(char *));
int ind = 0;
struct job *job = get_free_job();
struct proc *pipe_prev = NULL, *pr = NULL;
for (;; tokbuf++) {
if (tokbuf->type == TOK_STR) {
tmpargs[ind++] = tokbuf->str;
} else if (tokbuf->type == TOK_SPECIAL) {
switch (tokbuf->special) {
case '|':
pr = end_command(job, pr, &pipe_prev, tmpargs, &ind, 1);
break;
case '&':
pr = end_command(job, pr, &pipe_prev, tmpargs, &ind, 0);
end_job(&job, 1);
break;
case '<':
pr = end_command(job, pr, &pipe_prev, tmpargs, &ind, 0);
tokbuf++;
if (tokbuf->type == TOK_STR) {
connect_proc_file(pr, STDIN_FILENO, tokbuf->str,
0, 0);
} else {
pr_err("parser error");
}
}
} else if (tokbuf->type == TOK_NONE) {
end_command(job, pr, &pipe_prev, tmpargs, &ind, 0);
end_job(&job, 0);
break;
}
}
free(tmpargs);
free_token(tokstart);
return 0;
}
/* 1 on EOF, 0 on success, -1 on error */
int read_command(void)
{
flush_pre_prompt(stdout);
char *line;
if (line=readline("[bp]$ ")) {
if (line[0]) add_history(line);
process_line(line);
free(line);
check_jobs();
flush_pre_prompt(stdout);
return 0;
}
return 1;
}
#if 1
int main(void)
{
while (!read_command())
;
return 0;
}
#endif
io.h 0 → 100644
#ifndef _IO_H
#define _IO_H
enum token_type {
TOK_NONE,
TOK_STR,
TOK_SPECIAL,
};
struct token {
enum token_type type;
char *str; /* Only for TOK_STR */
char special;
};
/* String to be printed before next prompt */
void register_pre_prompt(const char *str);
/* str excludes nl */
int process_line(const char *str);
int read_command(void);
#endif /* _IO_H */
shell.h 0 → 100644
#ifndef _SHELL_H
#define _SHELL_H
#include <sys/types.h>
#include <stdlib.h>
/* Notes:
- All fds should have O_CLOEXEC, except 0, 1, 2. Once a process forks off,
it unsets the flag for the relevant fds.
*/
struct redir {
int used; /* 1 if this fd is to be used */
int close;
int fd;
};
enum proc_state {
PROC_INITIALISED,
PROC_RUNNING,
PROC_FINISHED,
PROC_ERROR,
};
#define MAX_REDIRECTED_FDS 10
#define TMP_FD (MAX_REDIRECTED_FDS + 1)
struct job;
struct proc {
enum proc_state state;
char *cmd;
char **argv;
int argc;
struct redir fds[MAX_REDIRECTED_FDS];
pid_t pid;
int wstatus; /* returned by waitpid */
struct job *job;
};
#define JOB_MAX_PROC 30
enum job_state {
JOB_UNALLOCATED=0,
JOB_INITIALISED,
JOB_RUNNING,
JOB_FINISHED,
};
#define PGRP_UNINITIALISED (-1)
struct job {
enum job_state state;
struct proc procs[JOB_MAX_PROC];
int num_procs;
int finished_procs;
int unused_fd_num;
pid_t pgrp;
int is_bg;
};
#define MAX_JOBS 30
struct proc *get_unused_proc(struct job *job);
int make_proc(struct proc *pr, const char *cmd, const char **argv,
struct job *job);
int connect_proc_fd(struct proc *pr, int fd_in_proc, int curr_fd,
int close_on_run);
/* Connect fd1 in proc1 to fd2 in proc2, so write to fd1 can be read from fd2 */
int connect_proc_proc(struct proc *proc1, int fd1,
struct proc *proc2, int fd2);
int connect_proc_file(struct proc *pr, int fd, const char *filename,
int write, int append);
/* Connect stdout->stdout, stdin->stdin, stderr->stderr */
int connect_proc_defaults(struct proc *pr);
/* Initialised */
struct job *get_free_job(void);
void free_job(struct job *j);
int start_job(struct job *job);
struct proc *lookup_proc_pid(pid_t pid);
/* Blocks for fg pgrp */
int check_jobs(void);
void pid_event(pid_t pid, int statloc);
#endif /* _SHELL_H */
utils.h 0 → 100644
#ifndef _UTILS_H
#define _UTILS_H
#include <stdarg.h>
#include <stdio.h>
static inline void __pr(const char *format, va_list l, FILE *f)
{
vfprintf(f, format, l);
fprintf(f, "\n");
}
static inline void pr_err(const char *format, ...)
{
va_list l;
va_start(l, format);
__pr(format, l, stderr);
va_end(l);
}
static inline void pr_dbg(const char *format, ...)
{
va_list l;
va_start(l, format);
__pr(format, l, stdout);
va_end(l);
}
#endif /* _UTILS_H */
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment