COMP1521 2022T2 Q10 · 进程/系统调用
COMP1521 2022T2 Q10 · 10 marks · C
知识点: 进程/系统调用(PROC) · 次: posix_spawn/PATH/glob/cd · 难度: 进阶
Topic: Processes & syscalls · Source: https://cgi.cse.unsw.edu.au/~cs1521/26T1/resources/practice_exams.html
一句话 / Summary: 实现简易shell rush五个feature
A shell
is an interactive prompt commonly found on UNIX systems that allows
a user to enter commands to perform, and displays the output of those
commands.
Your task in this question is to create a very simple shell that will
allow the user to enter commands and display the output of those commands.
We will call this shell "rush" — a feeling you may currently be experiencing.
The features of your shell have been allocated into smaller portions of marks,
as seen below.
mark feature
10% exit
20% cd and pwd
25% executing fully qualified commands
25% executing commands from the PATH
20% globbing arguments
Note that each feature depends on the previous one when testing and marking
your code, so please ensure you complete the features in order!
You have been provided with a file that prints prompts and reads input,
vageuly resembling a shell like bash ,
but currently missing all of the above functionality:
dcc 22t2final_q10.c -o 22t2final_q10
./22t2final_q10
[rush] exit
TODO: add your code here
[rush] cd ..
TODO: add your code here
[rush] pwd
TODO: add your code here
[rush] ./foo
TODO: add your code here
[rush] ls
TODO: add your code here
[rush] ls -l *
TODO: add your code here
As you can see, the program doesn't do much but print a
[rush] prompt, and a TODO message after
every input currently.
Once your program is working in its entirety,
the output may look something like this:
dcc 22t2final_q10.c -o 22t2final_q10
./22t2final_q10
[rush] cd /home/zac/stuff/
[rush] pwd
/home/zac/stuff
[rush] dcc hello.c -o hello
[rush] ./hello
Hello, world!
[rush] tail -n +1 ~/secrets/*
==> /home/zac/secrets/1521_master_password.txt <==
hunter2
==> /home/zac/secrets/diary.txt <==
spim bad
[rush] exit
In your provided starter code,
you will find:
All the #include s you should need for the exercise (you may add more).
Some convenience #define s that illustrate reasonable limits (you may add more).
A main function that sets up some variables and gets you started.
An is_executable function that is useful later for executing commands.
A do_glob function that is useful later for globbing.
Following, each feature will be described
for you to base your implementation on.
If you are unsure what your program should do
in a particular case,
we have provided a working reference solution for you.
Use 1521 rush to access the reference solution
and try to match your solution to its behaviour.
If 1521 rush crashes with an assertion failure,
it means that you do not have to consider that case.
Feature 1: exit
The exit command will exit the shell.
You should not produce any additional output when this command is run.
Any additional arguments should be ignored.
For example,
dcc 22t2final_q10.c -o 22t2final_q10
./22t2final_q10
[rush] exit
./22t2final_q10
[rush] exit foo
./22t2final_q10
[rush] exit foo bar baz
Feature 2: cd and pwd
The cd and pwd commands respectively
modify and display the shell's current working directory.
When the shell is first launched, the current working directory
is fetched with the getcwd(3) function
into the cwd variable
(this is already completed for you).
The cd command will change the
current working directory
to the specified path.
If the cd command is given no arguments,
the path should be assumed to be the user's home directory.
You can fetch the user's home directory with
the getenv(3) function,
requesting the HOME environment variable.
Changing the current working directory should be achieved
with the chdir(2) function.
You may also want to update the cwd variable
at this time, as mentioned previously.
If chdir(2) fails, you should print an error
messaging in the format:
fprintf(stderr, "cannot cd to %s\n", directory);
The pwd command will
print the current working directory.
This should be achieved either
with the getcwd(3) function
or the cwd variable,
depending on your particular approach.
The cd command should ignore
any arguments after the first one
( i.e. the path to change to),
and similarly the pwd command
should ignore any provided arguments.
For example,
dcc 22t2final_q10.c -o 22t2final_q10
pwd
/home/zac/stuff
./22t2final_q10
[rush] pwd
/home/zac/stuff
[rush] cd ..
[rush] pwd
/home/zac
[rush] cd /
[rush] pwd
/
[rush] cd
[rush] pwd
/home/zac
[rush] cd hdhhfdhjf
cannot cd to hdhhfdhjf
[rush]
Feature 3: executing qualified commands
For this feature, you will expand your shell to accept
qualified commands, which it will then execute.
"Qualified" in this context means that the command
is a path to a file, and your shell should simply
be able to execute that file with posix_spawn(3) .
You should "tokenize" input you have been given,
by splitting it on the space character.
This can be done with the strtok(3) function,
passing " " as the delim .
From this tokenization, the first token should
be considered the command to execute.
The remaining tokens should each be considered
as an individual argument to the command's argv .
Recall that programs expect argv[0] to be the command name
— so you should prepend the command name to the arguments.
You must also check that the command you are executing
is actually executable.
You can do this with the is_executable function.
If the command is not executable,
you should print an error message in the following format:
fprintf(stderr, "%s: cannot execute command\n", command); ,
and then continue the main loop to wait for further input.
You should then use the posix_spawn(3) function
to execute the command.
You can set the file_actions and attrp
arguments to NULL , but all other arguments should
be set correctly.
Finally, your shell should waitpid(2)
on the spawned process in order to ensure that the
shell does not continue before the command has finished.
For example,
dcc 22t2final_q10.c -o 22t2final_q10
./22t2final_q10
[rush] /bin/echo hello world!
hello world!
[rush] /bin/ls
22t2final_q10 22t2final_q10.c
[rush] /bin/ls -l
total 132
-rwxrwxr-x 1 zac zac 123192 Jan 01 12:00 22t2final_q10
-rw-rw-r-- 1 zac zac 5004 Jan 01 12:00 22t2final_q10.c
[rush] /bin/file 22t2final_q10.c
22t2final_q10.c: C source, ASCII text
[rush] ./hdhhfdhjf
hdhhfdhjf: cannot execute command
[rush] ./22t2final_q10.c
./22t2final_q10.c: cannot execute command
[rush]
Feature 4: executing commands from the PATH
Similarly to the previous feature, you should expand your shell to accept
unqualified commands, which it will then execute.
"Unqualified" in this context means that the command
is not an exact path to a file, but simply the name of a command
that will appear in the PATH .
You can tell whether a command is unqualified by checking
whether it has a '/' character in it
( e.g. with the strchr(3) function).
In this case, you need to search the
PATH for the requested command.
You can retrieve the PATH with
the getenv(3) function,
requesting the PATH environment variable.
You should then use the strtok(3) function
to tokenize the PATH on the : character.
Each token should be considered an individual directory to search.
For each directory, you should then use the is_executable
function to check whether a program with the requested name exists
and is executable in that directory
( snprintf(3) is useful to join the strings together).
Once you have found the first executable file,
you should then continue to execute it with posix_spawn(3)
as explained previously.
If you do not find an executable file,
you should print an error message in the same format as previously.
For example,
dcc 22t2final_q10.c -o 22t2final_q10
./22t2final_q10
[rush] echo hello world!
hello world!
[rush] ls
22t2final_q10 22t2final_q10.c
[rush] ls -l
total 132
-rwxrwxr-x 1 zac zac 123192 Jan 01 12:00 22t2final_q10
-rw-rw-r-- 1 zac zac 5004 Jan 01 12:00 22t2final_q10.c
[rush] file 22t2final_q10.c
22t2final_q10.c: C source, ASCII text
[rush] hdhhfdhjf
hdhhfdhjf: cannot execute command
[rush]
Note : you are not allowed to use
posix_spawnp(3) for this feature.
You must search the PATH manually,
and use posix_spawn(3) instead.
Feature 5: globbing arguments
For this feature,
you will need to modify 22t2final_q10.c
to support filename expansion,
sometimes referred to as globbing .
Any arguments to commands
( i.e. cd , qualified commands, unqualified commands)
should be fed into the glob(3) function in order
to expand any special characters,
such as ~ and * .
The exact usage of glob(3) has been provided for you
in the do_glob function. You can pass it text to glob
(in this case, arguments to commands),
and it will return a glob_t .
A glob_t contains the following fields that are
useful to us:
typedef struct {
size_t gl_pathc; / Count of glob expansions /
char **gl_pathv; / The list of glob expansions /
} glob_t;
The gl_pathc field contains the number of glob expansions,
recalling that a single argument may expand to multiple files
( e.g. with the '*' character).
This means that the program you intend to spawn
may have more arguments than you expected!
The gl_pathv field contains the list of glob expansions,
and it will have length gl_pathc .
Note that you need to glob the individual arguments to commands,
not the entire input line, nor the command itself.
This implies that commands such as ~/bin/my_program
will not work due to the '~' not being expanded,
but it simplifies your implementation.
For example,
dcc 22t2final_q10.c -o 22t2final_q10
./22t2final_q10
[rush] ls
22t2final_q10 22t2final_q10.c
[rush] echo my files are *
my files are 22t2final_q10 22t2final_q10.c
[rush] wc *
291 2308 123480 22t2final_q10
180 581 5458 22t2final_q10.c
471 2889 128938 total
[rush] cd ~/stuff
[rush] pwd
/home/zac/stuff
[rush]
other information
The autotests for this question will be difficult to understand
and will provide limited help when debugging your program.
You will need to do your own testing.
You only need to implement the features
and the functionality described in this question.
You do not have to develop an entire
implementation alike mainstream shells such as bash .
A line of input will be no longer than 1023 characters.
The current working directory will never be longer
than 1023 characters.
All specified commands
( i.e. exit , cd , pwd , qualified commands, unqualified commands)
will be no longer than 127 characters.
A command will never be provided more than 32 arguments.
An individual argument will never be longer than 127 characters.
The directory separating character can be assumed as '/' .
No error checking is necessary,
except that for which is required in order to
correctly implement the features described in this exercise
( e.g. checking if a program is executable).
Your solution must be in C only.
Your solution must not be in MIPS , C++ , Python , or any other language.
Your program cannot call external programs,
except those requested by the user's input.
You are not allowed to use system(3)
or an exec(3) -style function
in order to execute commands.
You must use posix_spawn(3) .
Your program may otherwise call functions from any C library
available by default on CSE Linux systems:
including e.g. stdio.h , stdlib.h ,
string.h , math.h , assert.h ).
🤖 Implement the rush shell in C exactly as specified (features:exit;cd/pwd; qualified commands viaposix_spawn; PATH search; argument globbing). The judge feeds commands on stdin and checks stdout: a[rush]prompt is printed before every line;pwdprints the cwd; command stdout is passed through;cd/exec errors go to stderr (not judged). Tests use only well-known dirs and a controlled glob directory so output is deterministic.
样例
exit
[rush]
cd / pwd exit
[rush] [rush] / [rush]
/bin/echo hello world exit
[rush] hello world [rush]