Professional Documents
Culture Documents
PROJECT REPORT
Submitted for CAL in B.Tech Operating Systems (CSE2005)
By
Slot: B1
November, 2017
CERTIFICATE
This is to certify that the Project work entitled Batch Job Executor that is being submitted by
Nikhilesh Prabhakar and Divyansh Choudhary for CAL in B.Tech Engineering Physics
Phy1001 is a record of bonafide work done under my supervision. The contents of this Project
work, in full or in parts, have neither been taken from any other source nor have been submitted
for any other CAL course.
Place : Chennai
This project was aimed at implementation of a Batch Job Executer. This was done by
understanding the concepts learnt in our Operating Systems course. System calls such as fork(),
exec(), dup(), and pipe(), were used and whose functionalities were understood. C programs are
used to simulate a working version of some of the most basic functionalities of a shell. The end
product made is a bash-like application that can take multiple-lines of commands, execute them
one by one and return all the outputs to a single text file.
1. Introduction:
As the main objective is to simply understand, appreciate and use the fork,
dup, exec and pipe system calls, were only going to be designing a
simplified version of the same. Some of the assumptions we are going to
make are:
(a) Batch file contains only single line commands. (i.e., commands are
not extended to multiple lines using \)
(b) Built-in shell commands are not used. (Can be easily incorporated,
but doesnt really contribute to understanding the system calls listed
above)
(c) Only single line comments using # preceded and followed by a
space are handled. (i.e., Handling of multi-line comments using /* ..
*/ is not supported)
(d) Only commands between a %BEGIN and a %END are executed.
All other commands are ignored.
(e) We redirect output to a specific file (here, output.txt). (Note that by
default, output is directed to stdout)
(f) Commands handled only include the |, > and >> operators (i.e.,
operators like & and < are not supported. But, the same code can
be easily extended to do the same)
Assumptions:
(1) We assume that the single line comments are marked as '# ' (i.e. #
followed by a space. Also, multi-line comments are ignored)
(2) We assume that built-in shell are not part of the input file. (i.e.,
these are not handled)
Simulation:
(i) Parsing
while(arg != NULL) {
// Don't parse after the beginning
// of a single-line comment is detected
if(strcmp(arg, "#") == 0) break;
args[pos] = arg;
pos = pos + 1;
if(pos > argsize) {
// reallocate memory for args
argsize = argsize + tok_size;
args = realloc(args, argsize*sizeof(char*));
if(!args) {
// Not enough space available.
// Return failure and exit.
printf("Memory allocation unsuccessful!
Exiting!\n");
exit(EXIT_FAILURE);
}
}
arg = strtok(NULL, tok_delimiters);
}
args[pos] = NULL;
return args;
}
Here, we use strtok() to tokenize the input. Here, we parse the command
(including the |, > or < operators, if any) to return an array of args,
which is later used to execute them.
Here, for correct parsing, all input in the batch file is assumed to have correct
(non-built-in functions only) input, separated by spaces.
(iii) Execution
Execute.c:
// Defined in execute.c
// Returns the length of the array of arguments
int argsLength(char **args) {
int i = 0;
while(args[i] != NULL) {
i++;
}
return i;
}
Iterate and get the positions(if present) of the >, >> and the |
operators (Irrelevant note: this implementation returns the position of the
last | found)
// ...
// Case: No |, > or >> operator. Redirect output to OUTPUT.txt
if(op1 == 0 && op2 == 0 && opPipe == 0) {
if((pid = fork()) < 0)
perror("Fork error");
else if(pid == 0) {
// Child.
// Execute command here
int fd = open("OUTPUT.txt", O_RDWR | O_CREAT | O_APPEND,
S_IRUSR | S_IRGRP | S_IWGRP |S_IWUSR);
dup2(fd, 1); // to make stdout go to file.
dup2(fd, 2); // to make stderr go to file.
close(fd);
printf("\n\n");
execvp(args[0], args);
printf("Couldn't execute this command\n");
}
else {
// wait for the child in parent process
do {
wpid = waitpid(pid, &status, 0);
} while(!WIFEXITED(status) && !WIFSIGNALED(status));
}
}
// ...
We use the fork() system call to fork a child process and this child process is
then used to execute the command.
For using fork() in a context like this, generally, your code always looks like
this:
// Using fork()
if ((pid = fork()) < 0) // fork error
perror("Fork error");
else if (pid == 0) // child process.
{
// do child process stuff
}
else // parent process
{
// do parent process stuff.
// in our case this only involves waiting for child to finish
}
Now, for the last part, i.e., to redirect the output to go to a file, we use the
dup2() system call.
The open() system call assigns the file descriptor of OUTPUT.txt to the
variable fd. Then, dup2(fd, 1) simply makes 1(which is the fd for stdout) be
the copy of fd, closing stdout first if necessary. Think of the command as
simply replacing stdout with OUTPUT.txt.
The parent process simply waits for the child process to finish execution.
The only difference here is that we have to modify our output file descriptor
accordingly.
if (op1 != 0) {
// '>' operator
args[op1] = NULL; // arguments must be null-terminated
// open in truncate mode - O_TRUNC
out_fd = open(args[op1 + 1], O_WRONLY | O_TRUNC | O_CREAT,
S_IRUSR | S_IRGRP | S_IWGRP |S_IWUSR);
}
else {
// '>>' operator
args[op2] = NULL;
// open in append mode - O_APPEND
out_fd = open(args[op2 + 1], O_CREAT | O_APPEND | O_WRONLY,
S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
}
// rest is the same as previous case
// replace stdout with this out_fd
dup2(out_fd, 1); // 1 equivalent to STDOUT_FILENO
close(out_fd);
printf("\n\n");
execvp(args[0], args);
}
For this particular case, we need to parse and extract the individual
commands that are being piped and store them in a separate array.
int n = numberOfPipes(args);
char **commands[n+1]; // Array of cmds after splitting on '|'
commands[k][j] = NULL;
k = k + 1;
curoffset = curoffset + 1;
}
else {
// No redirectio. So, use OUTPUT.txt
fout = open("OUTPUT.txt", O_RDWR | O_CREAT | O_APPEND,
S_IRUSR | S_IRGRP | S_IWGRP |S_IWUSR);
}
}
else {
// Use pipe for everything in between.
int pipefd[2];
pipe(pipefd);
fin = pipefd[0];
fout = pipefd[1];
}
dup2(fout, 1);
close(fout);
if((pid = fork()) < 0)
perror("Fork error");
else if(pid == 0) {
// child
execvp(commands[i][0], commands[i]);
printf("Couldn't execute this command\n");
}
else {
// parent
do {
wpid = waitpid(pid, &status, 0);
} while(!WIFEXITED(status) && !WIFSIGNALED(status));
}
This execution part is the same as the previous cases. The difference is in the
usage of the pipe() system call to redirect the input and output. pipefd[0] is
the read end of the pipe and pipefd[1] is the write end.
The dup2(fin, 0) ensures that fin replaces the stdin file descriptor. (Initially,
we set fin=dup(0), i.e., stdin)
Thus, what happens is, all the commands in between two pipes (i.e., |) read
the input from the read-end of the pipe and write to the write-end.
fp = fopen(argv[1], "r");
while(getline(&line, &linesize, fp) != -1) {
// ...
// If line is within a begin-end block, then, parse and
execute.
// Else, ignore the line.
// ...
}
// ...
}
conculusion
With the above assumptions, its easy to focus on just doing the important
stuff! (For, example, the whole thing can be made quite sophisticated using a
better parser or with a little extra code we can handle built-in commands. But
we dont really have to do all of that now!)
Analysis of the results - may contain plots, simulated results, synthesis of process,
Interpretation of the results. Comparison of the results with the published data and
deviations /improvements if any.
<Times New Roman, Font 12>
4. Conclusion
<Times New Roman, Font 16, Bold>