Last active
August 29, 2015 14:02
-
-
Save offlinehacker/57589067d7a3d89c9a80 to your computer and use it in GitHub Desktop.
My shell
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Compile with: g++ -Wall –Werror -o shell shell.c */ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <ctype.h> | |
#include <unistd.h> | |
#include <errno.h> | |
#include <limits.h> | |
#include <dirent.h> | |
#include <fcntl.h> | |
#include <sys/stat.h> | |
#include <sys/types.h> | |
#include <sys/wait.h> | |
/* The array below will hold the arguments: args[0] is the command. */ | |
static char* args[512]; | |
char *name[100]; | |
int status = 0; | |
#define READ 0 | |
#define WRITE 1 | |
typedef struct { | |
int pipes[2]; | |
pid_t pid; | |
int status; | |
} pdesc_t; | |
#define IN_CMD 0x1 | |
#define MIDDLE_CMD 0x2 | |
#define OUT_CMD 0x4 | |
#define RUN_FG 0x8 | |
typedef int (*command_t)(char*, char**); | |
#define DO_FORK 0x1 | |
typedef struct { | |
char name[20]; | |
command_t func; | |
int opts; | |
} command_desc_t; | |
pdesc_t *run_command(char **args, int input, int output, int opts, command_t command) | |
{ | |
int status; | |
pdesc_t *proc = (pdesc_t*)malloc(sizeof(pdesc_t)); | |
fflush(stdout); | |
// No worries about memory after fork, it gets copied | |
if ( pipe(proc->pipes) == -1 ) goto error; | |
if ( (proc->pid = fork()) == -1 ) goto error; | |
if ( proc->pid == 0 ) { | |
if ( opts & IN_CMD && input == 0 ) { | |
// First command | |
if ( dup2(input, STDIN_FILENO) == -1 ) goto child_error; | |
if ( dup2(proc->pipes[WRITE], STDOUT_FILENO) == -1 ) goto child_error; | |
} else if ( opts & MIDDLE_CMD && input != 0 ) { | |
// Middle command | |
if ( dup2(input, STDIN_FILENO) == -1 ) goto child_error; | |
if ( dup2(proc->pipes[WRITE], STDOUT_FILENO) == -1 ) goto child_error; | |
} else { | |
// Last command | |
if ( dup2(input, STDIN_FILENO) == -1 ) goto child_error; | |
if ( dup2(output, STDOUT_FILENO) == -1 ) goto child_error; | |
} | |
if ( (status = command( args[0], args )) == -1 ) goto child_error; | |
exit(status); | |
} | |
// Nothing more needs to be written | |
if ( close(proc->pipes[WRITE]) == -1 ) goto error; | |
// If it's the last command, nothing more needs to be read | |
if ( opts & OUT_CMD ) { | |
if ( close(proc->pipes[READ]) == -1 ) goto error; | |
} | |
return proc; | |
child_error: | |
exit(EXIT_FAILURE); | |
error: | |
perror("Error"); | |
return -1; | |
} | |
/* Final cleanup, 'wait' for processes to terminate. | |
* n : Number of times 'command' was invoked. | |
*/ | |
static int cleanup(int n) | |
{ | |
int i; | |
int status; | |
for (i = 0; i < n; ++i) wait(&status); | |
return status; | |
} | |
static int run(char* cmd, int input, int first, int last); | |
const char *trimwhitespace(char *str); | |
static int split(char* cmd, char **dest); | |
static char line[1024]; | |
int main( int argc, char *argv[] ) | |
{ | |
pdesc_t *proc; | |
FILE *input = stdin; | |
int interactive = 1; | |
if ( isatty(STDIN_FILENO) ) { | |
printf("♪┏(°.°)┛┗(°.°)┓┗(°.°)┛┏(°.°)┓ ♪\n"); | |
printf("MYsH: Type 'exit' or send EOF to exit.\n"); | |
} else { | |
interactive = 0; | |
} | |
FILE *output_fd = NULL; | |
FILE *input_fd = NULL; | |
strcpy(name, "mysh"); | |
while (1) { | |
if (input_fd) close(input_fd); | |
if (output_fd) close(output_fd); | |
/* Print the command prompt */ | |
if (interactive) printf("%s> ", name); | |
fflush(NULL); | |
/* Read a command line */ | |
if (!fgets(line, 1024, stdin)) return 0; | |
int input = STDIN_FILENO; | |
int output = STDOUT_FILENO; | |
int first = 1; | |
char* cmd = trimwhitespace(line); | |
size_t len = strlen(cmd); | |
char * redirect_to = memchr(cmd, '>', len); | |
if (redirect_to != NULL) *(redirect_to++) = '\0'; | |
char * redirect_from = memchr(cmd, '<', len); | |
if (redirect_from != NULL) *(redirect_from++) = '\0'; | |
char *bg = memchr(cmd, '&', len); | |
if (bg != NULL) *(bg++) = '\0'; | |
if ( redirect_to != NULL ) { | |
if ( (output_fd = fopen(trimwhitespace(redirect_to), "w")) == NULL ) { | |
perror("Error"); | |
continue; | |
} | |
output = fileno(output_fd); | |
} else output = STDOUT_FILENO; | |
if ( redirect_from != NULL ) { | |
if ( (input_fd = fopen(trimwhitespace(redirect_from), "r")) == NULL ) { | |
perror("Error"); | |
continue; | |
} | |
input = fileno(input_fd); | |
} else input = STDIN_FILENO; | |
cmd = trimwhitespace(cmd); | |
int count = 1; // Number of forks | |
char *tmp = strstr(cmd, "pipes"); | |
if (!(tmp && tmp==cmd)) { | |
char* next = strchr(cmd, '|'); /* Find first '|' */ | |
while (next != NULL) { | |
/* 'next' points to '|' */ | |
*next = '\0'; | |
if ( first ) { proc = run(cmd, input, output, IN_CMD); } | |
else { proc = run(cmd, input, output, MIDDLE_CMD);} | |
if ( proc == -1 ) goto exc_error; | |
cmd = trimwhitespace(next + 1); | |
next = strchr(cmd, '|'); /* Find next '|' */ | |
first = 0; | |
if ( proc->pid == -1 ) { input = 0; break; } | |
input = proc->pipes[READ]; | |
count ++; | |
free(proc); | |
} | |
proc = run(cmd, input, output, OUT_CMD); | |
if ( proc == -1 ) goto exc_error; | |
} else { | |
int x; | |
static char* pipes[512]; | |
int len = split(cmd, pipes); | |
for( x=1; x<len; x++ ) { | |
if (x>1) { free(proc); } | |
if ( x == len-1 ) { proc = run(pipes[x], input, output, OUT_CMD); } | |
else if ( x == 1 ) { proc = run(pipes[x], input, output, IN_CMD); } | |
else { proc = run(pipes[x], input, output, MIDDLE_CMD);} | |
if ( proc == -1 ) goto exc_error; | |
if ( proc->pid == -1 ) { input = 0; break; } | |
input = proc->pipes[READ]; | |
count++; | |
} | |
} | |
if ( proc && proc->pid == -1 ) { status = proc->status; free(proc); } | |
else if ( bg == NULL ) { status = cleanup(count); } | |
} | |
return 0; | |
exc_error: | |
fprintf(stderr, "Excution error!"); | |
exit(EXIT_FAILURE); | |
} | |
int to_int(char *str) { | |
char *endptr; | |
long val; | |
val = strtol(str, &endptr, 10); | |
/* Check for various possible errors */ | |
if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) | |
|| (errno != 0 && val == 0)) { | |
perror("Error: "); | |
return -1; | |
} | |
if (endptr == str) { | |
return -1; | |
} | |
return (int)val; | |
} | |
static int cmd_echo(char *cmd, char **args) { | |
args++; | |
while (args[0]) { printf("%s ", args[0]); args++; } | |
printf("\n"); | |
return 0; | |
} | |
static int cmd_print(char *cmd, char **args) { | |
args++; | |
while (args[0]) { printf("%s ", args[0]); args++; } | |
return 0; | |
} | |
static int cmd_status(char *cmd, char **args) { | |
printf("%d\n", status); | |
return status; | |
} | |
static int cmd_pid(char *cmd, char **args) { | |
printf("%d\n", getpid()); | |
return 0; | |
} | |
static int cmd_ppid(char *cmd, char **args) { | |
printf("%d\n", getppid()); | |
return 0; | |
} | |
static int cmd_exit(char *cmd, char **args) { | |
int exit_code; | |
if ( args[1] != NULL ) { | |
if( (exit_code = to_int(args[1])) == -1) { | |
fprintf(stderr, "Error: Error code invalid\n"); | |
return -1; | |
}; | |
exit(exit_code); | |
} else { | |
exit(EXIT_SUCCESS); | |
} | |
} | |
static int cmd_name(char *cmd, char **args) { | |
if (!args[1]) { | |
printf("%s\n", name); | |
return 0; | |
} | |
strcpy(name, args[1]); | |
return 0; | |
} | |
static int cmd_dir(char *cmd, char **args) { | |
int ret; | |
if (!args[1]) { | |
return chdir("/"); | |
} | |
if ( (ret = chdir(args[1])) == -1 ) perror("Error"); | |
return ret; | |
} | |
static int cmd_dirwhere(char *cmd, char **args) { | |
char *cwd = get_current_dir_name(); | |
printf("%s\n", cwd); | |
free(cwd); | |
return 0; | |
} | |
static int cmd_dirmake(char *cmd, char **args) { | |
int ret; | |
if (!args[1]) { | |
fprintf(stderr, "Error: Directory name not specified\n"); | |
return -1; | |
} | |
if ( (ret = mkdir(args[1], 0755)) == -1 ) perror("Error"); | |
return ret; | |
} | |
static int cmd_dirremove(char *cmd, char **args) { | |
int ret; | |
if (!args[1]) { | |
fprintf(stderr, "Error: Directory name not specified\n"); | |
return -1; | |
} | |
if ( (ret = rmdir(args[1])) == -1 ) perror("Error"); | |
return ret; | |
} | |
static int cmd_dirlist(char *cmd, char **args) { | |
DIR *dirp; | |
struct dirent *entry; | |
struct stat stat_buf; | |
struct stat *statp = &stat_buf; | |
if (!args[1]) args[1] = "."; | |
dirp = opendir(args[1]); | |
if (!dirp) goto error; | |
/* Reset errno on each iteration to detect readdir() errors */ | |
while (errno = 0, entry = readdir(dirp)){ | |
if (lstat(entry->d_name, statp) == -1) goto error; | |
printf("%s ", entry->d_name); | |
} | |
if (errno) goto error; | |
closedir(dirp); | |
printf("\n"); | |
return 0; | |
error: | |
perror("Error"); | |
return -1; | |
} | |
static int cmd_linkhard(char *cmd, char **args) { | |
int ret; | |
if (!args[1]) { | |
fprintf(stderr, "Error: Source file not specified\n"); | |
return -1; | |
} | |
if (!args[2]) { | |
fprintf(stderr, "Error: Link name not specified\n"); | |
return -1; | |
} | |
if ( (ret = link(args[1], args[2])) == -1 ) perror("Error"); | |
return ret; | |
} | |
static int cmd_linksoft(char *cmd, char **args) { | |
int ret; | |
if (!args[1]) { | |
fprintf(stderr, "Error: Source file not specified\n"); | |
return -1; | |
} | |
if (!args[2]) { | |
fprintf(stderr, "Error: Link name not specified\n"); | |
return -1; | |
} | |
if( (ret = symlink(args[1], args[2])) == -1 ) perror("Error"); | |
return ret; | |
} | |
static int cmd_linkread(char *cmd, char **args) { | |
int ret; | |
char path[PATH_MAX]; | |
if (!args[1]) { | |
fprintf(stderr, "Error: Link name not specified\n"); | |
return -1; | |
} | |
if( (ret = readlink(args[1], path, PATH_MAX)) == -1 ) perror("Error"); | |
else printf("%s\n", path); | |
return ret; | |
} | |
static int cmd_unlink(char *cmd, char **args) { | |
int ret; | |
char path[PATH_MAX]; | |
if (!args[1]) { | |
fprintf(stderr, "Error: File name not specified\n"); | |
return -1; | |
} | |
if( (ret = unlink(args[1])) == -1 ) perror("Error"); | |
return ret; | |
} | |
static int cmd_rename(char *cmd, char **args) { | |
int ret; | |
if (!args[1]) { | |
fprintf(stderr, "Error: Source file not specified\n"); | |
return -1; | |
} | |
if (!args[2]) { | |
fprintf(stderr, "Error: Destination file not specified\n"); | |
return -1; | |
} | |
if( (ret = rename(args[1], args[2])) == -1 ) perror("Error"); | |
return ret; | |
} | |
int cpcat(int fd_to, int fd_from) | |
{ | |
char buf[4096]; | |
ssize_t nread; | |
while (nread = read(fd_from, buf, sizeof buf), nread > 0) { | |
char *out_ptr = buf; | |
ssize_t nwritten; | |
do { | |
nwritten = write(fd_to, out_ptr, nread); | |
if (nwritten >= 0) { | |
nread -= nwritten; | |
out_ptr += nwritten; | |
} | |
else if (errno != EINTR) return -1; | |
} while (nread > 0); | |
} | |
if (nread == 0) { | |
if (close(fd_to) < 0) return -1; | |
return 0; | |
} | |
} | |
static int cmd_cpcat(char *cmd, char **args) { | |
int ret, fd_to, fd_from; | |
char path[PATH_MAX]; | |
if (!args[1]) { | |
fd_from = STDIN_FILENO; | |
fd_to = STDOUT_FILENO; | |
} else { | |
if ( (fd_from = open(args[1], O_RDONLY)) == -1 ) goto out_error; | |
if (!args[2]) fd_to = STDOUT_FILENO; | |
else { | |
if ( (fd_to = open(args[2], O_WRONLY | O_CREAT | O_EXCL, 0666)) == -1 ) return -1; | |
} | |
} | |
if( (ret = cpcat(fd_to, fd_from)) == -1 ) goto out_error; | |
return ret; | |
out_error: | |
perror("Error"); | |
if (fd_from != STDIN_FILENO) close(fd_from); | |
if (fd_to != STDOUT_FILENO) close(fd_to); | |
return -1; | |
} | |
static int cmd_listhard(char *cmd, char **args) { | |
DIR *dirp; | |
struct dirent *entry; | |
struct stat stat_buf; | |
struct stat *statp = &stat_buf; | |
ino_t inode; | |
if (!args[1]) { | |
fprintf(stderr, "Error: Link file not specified\n"); | |
return -1; | |
} | |
if (stat(args[1], statp) == -1) goto error; | |
inode = statp->st_ino; | |
dirp = opendir("."); | |
if (!dirp) goto error; | |
/* Reset errno on each iteration to detect readdir() errors */ | |
while (errno = 0, entry = readdir(dirp)){ | |
if (lstat(entry->d_name, statp) == -1) goto error; | |
if (inode == statp->st_ino) printf("%s ", entry->d_name); | |
} | |
if (errno) goto error; | |
closedir(dirp); | |
printf("\n"); | |
return 0; | |
error: | |
perror("Error"); | |
return -1; | |
} | |
static int cmd_help(char *cmd, char **args); | |
command_desc_t commands[] = { | |
{"help", (command_t)cmd_help, DO_FORK}, | |
{"echo", (command_t)cmd_echo, DO_FORK}, | |
{"print", (command_t)cmd_print, DO_FORK}, | |
{"status", (command_t)cmd_status, DO_FORK}, | |
{"dirwhere", (command_t)cmd_dirwhere, DO_FORK}, | |
{"dirmake", (command_t)cmd_dirmake, DO_FORK}, | |
{"dirremove", (command_t)cmd_dirremove, DO_FORK}, | |
{"dirlist", (command_t)cmd_dirlist, DO_FORK}, | |
{"linkhard", (command_t)cmd_linkhard, DO_FORK}, | |
{"linklist", (command_t)cmd_listhard, DO_FORK}, | |
{"linksoft", (command_t)cmd_linksoft, DO_FORK}, | |
{"linkread", (command_t)cmd_linkread, DO_FORK}, | |
{"unlink", (command_t)cmd_unlink, DO_FORK}, | |
{"rename", (command_t)cmd_rename, DO_FORK}, | |
{"cpcat", (command_t)cmd_cpcat, DO_FORK}, | |
{"dir", (command_t)cmd_dir, 0}, | |
{"name", (command_t)cmd_name, 0}, | |
{"exit", (command_t)cmd_exit, 0}, | |
{"pid", (command_t)cmd_pid, 0}, | |
{"ppid", (command_t)cmd_ppid, 0} | |
}; | |
static int cmd_help(char *cmd, char **args) { | |
int x; | |
for( x=0; x<sizeof(commands)/sizeof(command_desc_t); x++) | |
printf("%s\n", commands[x].name); | |
return 0; | |
} | |
static int cmd_exec(char *cmd, char **args) { | |
int ret; | |
if ( (ret = execvp(cmd, args)) == -1 ) { perror("Error"); } | |
return ret; | |
} | |
static int run(char* cmd, int input, int output, int opts) { | |
int x; | |
pdesc_t *proc = (pdesc_t*)malloc(sizeof(pdesc_t)); | |
split(cmd, args); | |
// Skip comments or blank commands | |
if ( args[0] == NULL || args[0][0] == '#' || args[0][0] == '\n' ) { | |
proc = (pdesc_t*)malloc(sizeof(pdesc_t)); | |
proc->status = status; | |
proc->pid = -1; | |
return proc; | |
} | |
// Run internal commands | |
for( x=0; x<sizeof(commands)/sizeof(command_desc_t); x++) { | |
if ( strcmp(commands[x].name, args[0]) == 0 ) { | |
if ( commands[x].opts & DO_FORK ) { | |
return run_command(args, input, output, opts, commands[x].func); | |
} else { | |
proc->pid = -1; | |
proc->status = commands[x].func(args[0], args); | |
return proc; | |
} | |
} | |
} | |
// Run external command | |
free(proc); | |
return run_command(args, input, output, opts, (command_t)cmd_exec); | |
} | |
const char* skipwhite(char* s) | |
{ | |
while (isspace(*s)) ++s; | |
return s; | |
} | |
const char *trimwhitespace(char *str) | |
{ | |
char *end; | |
// Trim leading space | |
while(isspace(*str)) str++; | |
if(*str == 0) // All spaces? | |
return str; | |
// Trim trailing space | |
end = str + strlen(str) - 1; | |
while(end > str && (isspace(*end) || *end == '\n')) end--; | |
// Write new null terminator | |
*(end+1) = 0; | |
return str; | |
} | |
const char * strip(char* s) { | |
int len = strlen(s); | |
if (s[0] == '"') | |
if (s[len-1] == '"') | |
(s++)[len-1] = '\0'; | |
return s; | |
} | |
static int split(char* cmd, char **dest) | |
{ | |
char tok; | |
if (cmd[0] == '"') {tok = '"'; cmd++;} else tok = ' '; | |
char* next = strchr(cmd, tok); | |
int i = 0; | |
while(next != NULL) { | |
next[0] = '\0'; | |
dest[i] = cmd; | |
++i; | |
cmd = skipwhite(next + 1); | |
if (cmd[0] == '"') {tok = '"'; cmd++;} else tok = ' '; | |
next = strchr(cmd, tok); | |
} | |
if (cmd[0] != '\0') args[i] = strip(cmd); | |
else args[i] = NULL; | |
dest[++i] = NULL; | |
return i-1; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment