This homework is about how system/user information can be obtained via shell commands as well as C function.
The system
command allows you to call a shell function
from C code. It returns an integer noting success/failure.
The argument to it should be a string constant.
You may be able to get around this, though it is not a good idea
since it can introduce a security weakness.
Consider the following program.
#include <stdio.h>
#include <stdlib.h> // defines "system"
int main (int argc, char *argv[]) {
int returnValue;
returnValue = system("echo hello world");
printf("The command returned with value %d.\n", returnValue);
return 0;
}
Compiling it and running it shows:
cascade:c++files> ./a.out hello world The command returned with value 0.Notice that the program called the "echo" command, and the output went to the terminal. The C program does not "see" the output, only the return value.
Here is a bad example. Suppose that we issue the following command.
returnValue = system("hello world");The program will compile, but there is no shell command called "hello", so it will generate an error. The return value will be non-zero.
Now suppose that you compiled the "hello world" program (the one that uses printf) to an executable named "hello", and that it is in the current directory. The above command will still fail, however, if you change it to
returnValue = system("./hello");then it will work. That is, you can use
system
to
call programs that you write yourself.
The programs called could be other compiled programs,
or could be shell scripts.
The next example gets the ID number of the current process, followed by the ID number belonging to the parent of that process. "pid_t" is an integer type used for process IDs. You could use "int" instead, but it is good practice to use "pid_t".
pid_t my_id; my_id = getpid(); printf("syscall returned %d as the process id.\n", my_id); my_id = getppid(); printf("syscall returned %d as the id of the parent process.\n", my_id);See gnu.org page on process identification for more info.
cascade:c++files> ./a.out syscall returned 1373 as the process id. syscall returned 607 as the id of the parent process. cascade:c++files> ./a.out syscall returned 1374 as the process id. syscall returned 607 as the id of the parent process.Notice how the ID of the parent process is the same in both, since the shell has not changed. The process ID changes, though, such as 1373 only lasted until the end of the run. On the next run, the new process is assigned the ID 1374.
Use the following command to get the user ID:
my_id = getuid();The group ID can be found with the following.
my_id = getgid();
The shell commands invoked by "system" create a new shell each time,
meaning that it does not preserve the state of the former commamd.
So if we do ls
then cd
then ls
again,
as three separate system commands,
the results of the two ls
commands will be the same.
Consider the following code:
printf("Calling the pwd command...\n"); returnValue = system("pwd"); printf(" return value is %d.\n", returnValue); printf("Now do \"cd /etc\" ...\n"); returnValue = system("cd /etc"); printf(" return value is %d.\n", returnValue); printf("Calling the pwd command...\n"); returnValue = system("pwd"); printf(" return value is %d.\n", returnValue);When run, the output looks like this.
Calling the pwd command... /Users/mweeks/programs/c++files return value is 0. Now do "cd /etc" ... return value is 0. Calling the pwd command... /Users/mweeks/programs/c++files return value is 0.The
pwd
command gives the same result each time, even though the
cd
command changed the directory to "/etc", at least momentarily.
That is, a new shell was created to handle the cd
command,
and it did change the directory. But then that shell terminated at the
end of the system
command, and the directory reverted back
to what it was when that shell process was created.
What if you want to change the directory, in a way that lasts?
There is a chdir
command.
Here is an example, replacing the system("cd /etc")
line
from the previous example with this following line.
returnValue = chdir("/etc");This time, we get the following output.
Calling the pwd command... /Users/mweeks/programs/c++files return value is 0. Now do "chdir()" ... return value is 0. Calling the pwd command... /private/etc return value is 0.Notice how the
pwd
command gives a different result
the second time it is called.
The "/etc" directory on this particular computer maps to "/private/etc".
After the process terminates, the computer reverts to the original
directory. That is, the chdir
function only affects the
current directory of the process.
sleep
function is sometimes necessary.
As you might guess from the name, it causes the process to "sleep"
for a specified amount of time. One such use of this function is
to periodically check for some condition to change or event to occur.
Yes, you could have the check in a while loop without the "sleep"
function, however it will drag down the system's performance.
Calling "sleep" means that other processes get the chance to run.
Next is a short example demonstrating the "sleep" function.
#include <unistd.h> // for sleepThen later in the program:
printf("Starting...\n"); sleep(10); printf("Now it is 10 seconds later.\n");When run, the process prints the "Starting..." message. Then the process "sleeps" for 10 seconds. After this, it prints the second message.
A shell script can simply be a collection of commands that you have entered at the command prompt. For example, put the following into a text file. (Yes, you should do this. No, you do not have to turn it in.) It is good practice to name a shell script with an extension of ".sh", though you may see shell scripts with a ".bash" extension if they are specifically using Bash commands. Click here for an example bash script.
echo "Calling pwd"
pwd
echo "done"
Use the chmod
command to make the text file executable.
Then, run it by typing "./" in the shell followed by the name,
just like we use to run compiled C programs.
To find the parent process, use $PPID
.
For the current process, use $$
.
cascade:Desktop> echo $PPID
562
cascade:Desktop> echo $$
570
Get the username by utilizing the $USER variable. This is set automatically;
and you do not need to define it.
Likewise, the user ID is stored in $UID
.
cascade:Desktop> echo "Hello $USER"
Hello mweeks
cascade:Desktop> echo $UID
501
The group ID can be found with $GID
.
cascade:Desktop> echo $GID
20
Next, we can call "sleep" and "ps" easily from the shell.
cascade:Desktop> sleep 2
cascade:Desktop> ps
PID TTY TIME CMD
570 ttys000 0:00.10 -zsh
572 ttys001 0:00.01 -zsh
571 ttys002 0:00.01 -zsh
595 ttys003 0:00.01 -zsh
cascade:Desktop>
Process 562 in this case is not shown, due to defaults with the process
status (ps) command. To see all process information for a user,
use "ps -U username".
You do not need to use the "-U username" for this assignment.
Here we will compare a couple of ways of doing things. You will create and turn in two programs: one is a C program, and the other is a shell script.
Start your program with the message "Starting C program". At the end of program, print the message "Ending C program".
Use getpid
and getppid
, and show the values of
each of these along with an appropriate message saying what they are.
Also get the group ID, and print this using an appropriate message.
Get the user ID. If the user ID matches your account, print a "Welcome your name" message, and then use "grep" with your user ID for the file /etc/passwd. You can hard-code your user ID into the grep string. If the user ID does not match your account, print the user ID using an appropriate message, but do not print the "Welcome" message and do not use "grep" on your user ID.
Note that the "system" call is a general-purpose function.
returnValue = system("ps");This example returns text, however, it goes to the terminal (or more specifially, to
stdout
).
The calling program does not "see" the resulting output.
In your C program, use this command:
returnValue = system("ps");and include an appropriate message before it. If you want more information, there is code showing
system("ps");
in the Unix book,
e.g. on page 482,
but "system" does not appear to be in the index.
This command (system) is in the C book on page 689.In C, get a list of processes (with "ps" as a system command). Then call the sleep function for 3 seconds (with "sleep" as a system command). Get a list of processes again, and compare it to the first list of processes. Is the list of processes the same?
Next, create a shell script to give the same output as your C program, with following exceptions. "Starting C program" should be "Starting shell script". "Ending C program" should be "Ending shell script". The output from "ps" will be slightly different.
Also, you can do conditional statements in a shell script, but we
will not do this here.
Instead, use $USER within the welcome message,
i.e. "Welcome $USER". Then use grep
with the $USER variable
for the file /etc/passwd.
That is, the shell script version will look for the username in the /etc/passwd
file regardless of who runs your program.
GID
is defined under some shells,
such as zsh, however it is not defined under bash. You can define this
yourself with the following command:
GID=`id -g`
The back-ticks say to perform a command, in this case id -g
which returns the group ID.
The GID=
part is an assignment, so it will create the variable
GID
and set it to the result of the id
command.
/etc/passwd
file does not contain student account information. Instead, the
login system handles authentication in another way, which allows you to
use the same password as on other services.
For the homework, you should still perform
the grep of /etc/passwd
command,
but it's OK that it returns nothing.