C shell: kill process syntax - c

I'm trying to write my own C shell. I'm wondering how to make calling 'kill' in the command line work. For example,
shell> kill 2
shell: process 2 has been killed
Line 1 is user input 'kill 2'
Line 2 is program-printed message of what has been done.
I know I have to take the pid as the argument I believe and send the SIGKILL signal.
Using something like
kill(pid, SIGKILL);
How do I connect this kill function to respond when a user inputs 'kill 2' in a C implementation? I guess I'm having a hard time connecting it to the command line arguments for implementation. I might need strtok/atoi?
Thank you.

Better you go for "getopt" which might look as fallows,
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <signal.h>
int pid;
if((pid = getopt(argc, argv, "0123456789")) != -1)
if(isdigit(pid)){
if(kill(pid, SIGKILL) == -1){
perror("KILL:");
exit(0);
}
}else{
printf("Input format: kill <pid>");
}

#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
int main(int argc, char **argv)
{
if (argc < 3)
{
printf("usage: ./kill OPERATION(kill/cont) PID\n");
return -1;
}
if(strcmp(argv[1],"kill") == 0 )
{
printf("Kill:\n");
kill(atoi(argv[2]), SIGKILL);
}
else if(strcmp(argv[1],"cont") == 0)
{
printf("cont:\n");
kill(atoi(argv[2]), SIGCONT);
}
else
{
printf("Kill default:\n");
kill(atoi(argv[2]), SIGKILL);
}
return 0;
}

Related

If the subprocess does not call the system call, will the signal sent by kill still take effect?

If the subprocess does not call the system call, will the signal sent by kill still take effect?
It worked. But I want to know when did the subprocess enter the kernel mode.
Code as follows.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
int pid;
if ((pid = fork()) == 0)
{
while (1)
continue;
exit(0);
}
sleep(3);
kill(pid, SIGINT);
int wid, status;
while ((wid = wait(&status)) != -1)
{
printf("child %d: exited with ", wid);
if (WIFEXITED(status))
printf("%d\n", WEXITSTATUS(status));
if (WIFSIGNALED(status))
printf("%d\n", WTERMSIG(status));
}
return 0;
}
Yes, processes that do not make system calls can still receive signals. You can easily test this by writing an infinite loop program, running it, then pressing Ctrl-C, or using the kill command from another terminal window.

Redirection of stdin and stdout via pipes in C works for external programmes but not for recursive call

I am trying to communicate with forked child processes via pipe redirection of stdin and stdout in C. I already managed to get this to work for shell commands (like ls, for example) executed in child processes. However, I wasn't able to recursively execute the same program and redirect the output (printed by printf(), fprintf() to stdout, ...) via the pipes from the child process to the parent (in this test to stdout of the parent), although this works fine for ls or similar commands.
Here's how I tried to approach this:
I create a pipe, the reading end is for the parent, the child process should write to the writing end.
The Process forks, both processes close the unused end, respectively.
The writing end of the pipe is redirected to STDOUT_FILENO and closed
The child process executes the program recursively (it is called ./to2)
As mentioned, this does work if I execute ls in the child process, but not if I try to call the same program recursively. Here's my test program where I tried to get this to work:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <netdb.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <fcntl.h>
static void usage(void){
fprintf(stderr,"RIP");
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]){
if(argc > 1){
dprintf(STDOUT_FILENO,"Please work\n");
printf("\n THIS IS A MESSAGE FROM THE CHILD \n");
fputs("Pretty Please!\n",stdout);
fflush(stdout);
exit(EXIT_SUCCESS);
}
int p1[2];
if(-1 == pipe(p1)) {
fprintf(stderr,"pipe\n");
fprintf(stderr,"%s\n",strerror(errno));
usage();
}
int f = fork();
if(f == 0){
close(p1[0]);
if(dup2(p1[1],STDOUT_FILENO) < 0){
fprintf(stderr,"dup2\n");
usage();
}
close(p1[1]);
//I want this to work:
//execlp("./to2", "./to2", "-e");
//This works fine:
execlp("ls", "ls");
exit(EXIT_SUCCESS);
} else if (f == -1) {
usage();
} else {
close(p1[1]);
int w = -1;
if(-1 == wait(&w)) usage();
char b[12];
memset(b,0,12);
read(p1[0],&b,12);
char reading_buf[1];
while(read(p1[0], reading_buf, 1) > 0){
write(1, reading_buf, STDOUT_FILENO);
}
close(p1[0]);
}
}
For testing purposes, the function is called recursively with additional arguments, while the parent program is called without additional arguments (hence the if(argc>1)).
In the final program, endless recursion is being avoided by other means.
Did I understand something wrongly? I am pretty confused by the fact that the only thing that doesn't seem to work is redirecting the output of my own
program...
Thank you very much in advance, any help or ideas are greatly appreciated.
The primary problem is precisely as outlined in the comments — you are not calling execlp() correctly (nor ls in the alternative). You must make the last argument on those function calls into an explicit null pointer, as shown in this code, which is a mostly mildly edited version of what's in the question:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
static void usage(void)
{
fprintf(stderr, "RIP\n");
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
if (argc > 1)
{
dprintf(STDOUT_FILENO, "Please work\n");
printf("THIS IS A MESSAGE FROM THE CHILD\n");
fputs("Pretty Please!\n", stdout);
fflush(stdout);
exit(EXIT_SUCCESS);
}
int p1[2];
if (-1 == pipe(p1))
{
fprintf(stderr, "pipe: %s\n", strerror(errno));
usage();
}
int f = fork();
if (f == 0)
{
close(p1[0]);
if (dup2(p1[1], STDOUT_FILENO) < 0)
{
fprintf(stderr, "dup2: %s\n", strerror(errno));
usage();
}
close(p1[1]);
execlp(argv[0], argv[0], "-e", (char *)0);
fprintf(stderr, "failed to exec %s again\n", argv[0]);
exit(EXIT_FAILURE);
}
else if (f == -1)
{
usage();
}
else
{
close(p1[1]);
char b[13];
memset(b, 0, 13);
if (read(p1[0], &b, 12) < 0)
{
fprintf(stderr, "Failed to read from pipe (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
}
int len = strcspn(b, "\n");
printf("M1 [%.*s]\n", len, b);
char reading_buf[1];
while (read(p1[0], reading_buf, 1) > 0)
{
write(1, reading_buf, STDOUT_FILENO);
}
close(p1[0]);
int w = -1;
if (-1 == wait(&w))
usage();
}
return 0;
}
Two important changes should be highlighted:
This code echoes the first line of data — the one written by dprintf() — whereas the original code just read it and discarded it.
The wait() call is after the input, not before. If the child had more data to write than a set of fixed messages, it could block waiting for the parent to read some of the data, while the parent is blocked waiting for the child to exit. This would be a deadlock.
The usage() function is not appropriately named — it doesn't report how to run the program. I also exit with a failure status, not success, if the child process fails the execlp().
Under peculiar circumstances, the wait() call might report on the exit status from some child other than the one that was forked. It is generally best to use a loop to reap such children. However, the circumstances required are extremely peculiar — the process which launched the parent with an exec*() function must have previously created some children for which it didn't wait, so that they are inherited by the parent process (because the PID doesn't change across an exec*() call).

Ctrl+Z Signal handling in C

I am writing a simple shell in C.
However, I found that my program cannot properly handle the Ctrl+Z signal. My program looks like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
void interpreter() {
char input[256];
int i;
char dir[PATH_MAX+1];
char *argv[256];
int argc = 0;
char *token;
if (getcwd(dir, PATH_MAX+1) == NULL) {
//error occured
exit(0);
}
printf("[shell:%s]$ ", dir);
fgets(input,256,stdin);
if (strlen(input) == 0) {
exit(0);
}
input[strlen(input)-1] = 0;
if (strcmp(input,"") == 0) {
return;
}
token = strtok(input, " ");
while(token && argc < 255) {
argv[argc++] = token;
token = strtok(NULL, " ");
}
argv[argc] = 0;
pid_t forknum = fork();
if (forknum != 0) {
int status;
waitpid(forknum, &status, WUNTRACED);
} else {
signal(SIGINT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
setenv("PATH","/bin:/usr/bin:.",1);
execvp(argv[0], argv);
if (errno == ENOENT) {
printf("%s: command not found\n", argv[0]);
} else {
printf("%s: unknown error\n", argv[0]);
}
exit(0);
}
}
int main() {
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
while(1) {
interpreter();
}
}
I have ignored above signals in the main process.
When I start cat(1) and then hit Ctrl+Z, the next line of input will still be captured by the cat(1) program rather than my main process. It means that my main process will do nothing but if I wake up the cat(1) program, it will output what I typed immediately. All things go back to normal after this.
I can't figure out how to resolve this. I am still not sure if I have stated it clearly.
Interesting. Even though this is tagged Linux, I'll go out on a limb and say that you are running this on OS X.
When compiled on Linux, the problem is not there, but on Mac it happens exactly as you described. It looks like a bug in OS X: because both the shell process and cat(1) are on the same process group (since you don't explicitly change group membership), it seems like OS X makes the mistake of feeding the next input line to the fgets(3) call that is asleep in the cat(1) process, so you end up losing that line of input from the shell process (because it is consumed by the sleeping cat(1)).
The reason this doesn't happen with bash is because bash supports job control, and as such processes are put in separate process groups (in particular, bash chooses the first process of a process pipeline as the process group leader). So when you do the same thing on bash, each invocation of cat(1) ends up putting it in a separate process group (and then the shell controls which process group is in the foreground with tcsetpgrp(3)). So, at any time, it is clear which process group has control over terminal input; the moment you suspend cat(1) in bash, the foreground process group is changed to bash again and input is read successfully.
If you do the same as bash in your shell, it will work in Linux, OS/X, and basically any other UNIX variant (and it is how other shells do it too).
In fact, if you want your shell to have job support, you'll have to do this sooner or later (learn about process groups, sessions, tcsetpgrp(3), setpgid(2), etc.).
So, in short, do the right thing if you want job support and wrap the forked process in a new process group:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
void interpreter() {
char input[256];
char dir[PATH_MAX+1];
char *argv[256];
int argc = 0;
char *token;
if (getcwd(dir, PATH_MAX+1) == NULL) {
//error occured
exit(0);
}
printf("[shell:%s]$ ", dir);
fgets(input,256,stdin);
if (strlen(input) == 0) {
exit(0);
}
input[strlen(input)-1] = 0;
if (strcmp(input,"") == 0) {
return;
}
token = strtok(input, " ");
while(token && argc < 255) {
argv[argc++] = token;
token = strtok(NULL, " ");
}
argv[argc] = 0;
pid_t forknum = fork();
if (forknum != 0) {
setpgid(forknum, forknum);
signal(SIGTTOU, SIG_IGN);
tcsetpgrp(STDIN_FILENO, forknum);
tcsetpgrp(STDOUT_FILENO, forknum);
int status;
waitpid(forknum, &status, WUNTRACED);
tcsetpgrp(STDOUT_FILENO, getpid());
tcsetpgrp(STDIN_FILENO, getpid());
} else {
setpgid(0, getpid());
signal(SIGINT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
setenv("PATH","/bin:/usr/bin:.",1);
execvp(argv[0], argv);
if (errno == ENOENT) {
printf("%s: command not found\n", argv[0]);
} else {
printf("%s: unknown error\n", argv[0]);
}
exit(0);
}
}
int main() {
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
while(1) {
interpreter();
}
}
(Although, admittedly, it is unfortunate that OS X does such a poor job in this situation - you really shouldn't have to do this).
The changes are just inside the process-specific code: both the child and the parent call setpgid(2) to make sure that the newborn process is indeed in a single process group before either the parent of the process itself assumes that this is already true (this pattern is recommended in Advanced Programming in the UNIX Environment); the tcsetpgrp(3) call must be invoked by the parent.
Of course, this is far from complete, you then need to code the necessary functions to bring a job back to the foreground, list jobs, etc. But the code above works with your test scenario nonetheless.
Nitpick: you should be using sigaction(2) instead of the deprecated, unreliable and platform-dependent signal(3), but it's a minor issue here.

how to use the tcsetgprp function

this is a simple example to use the tcsetpgrp function:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
static void judge(void){
pid_t pid;
pid = tcgetpgrp(STDIN_FILENO);
if(pid == -1){
perror("tcgetpgrp");
return;
}else if(pid == getpgrp()){
printf("foreground\n");
}else{
printf("background\n");
}
}
int main(void){
signal(SIGTTOU,SIG_IGN);
judge();
int result;
result = tcsetpgrp(STDIN_FILENO,getpgrp());
if(result == -1){
perror("tcsetpgrp");
return -1;
}
judge();
return 0;
}
i will run it in the background,as my expection,the output like:
todd911#virtual-machine:./a.out &
[1] 15894
todd911#virtual-machine:~$ background
foreground
but in fact,it act like:
todd911#virtual-machine:./a.out &
[1] 15894
todd911#virtual-machine:~$ background
foreground
exit
at last,the terminal exit automatically,is anybody know the reason?
It is possible that if the process group that controls the terminal exits, then the shell or terminal will decide the session has ended and kill it off. I tried your program on gnome-terminal with ksh93 and bash and it did not exit.

simple shell using execlp goes weirdly

I am reading GNU/Linux application programming the 2nd edition,you can reach what am reading from here.After I write the code similar to his,but it work strangely:
$ ./shell
./shell>>quit
$ ./shell
./shell>>date
Sun Aug 8 21:19:37 CST 2010
./shell>>quit
$ ./shell
./shell>>abc
execlp failed: No such file or directory
./shell>>quit
./shell>>quit
$./shell
./shell>>abcd execlp
execlp failed: No such file or directory
./shell>>quit
./shell>>quit
The first and second cases are ok,but the third and forth ones somewhat need two quit to quit.This is not what i am expecting.I guess something is wrong with fork(),or the waitpid(),but this still got unsolved after asking a few people around me.Now is summer time, i.e. summer holiday,I got no more mates to go for.Thanks always.
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define LINE_LEN 80
int main(int argc,char* argv[])
{
pid_t pid;
char cmd[LINE_LEN+1]={'\0'};
while(1)
{
printf("%s>>",argv[0]);
if(fgets(cmd,sizeof(cmd),stdin)==NULL)
{
perror("fgets failed");
break;
}
cmd[strlen(cmd)-1]='\0';
if(strncmp(cmd,"quit",4)==0)
{
break;
}
if((pid=fork())==-1)
{
perror("fork failed");
break;
}else if(pid==0)
{
//TODO no option can be specified for cmd
execlp(cmd,cmd,NULL);
perror("execlp failed");
}else
{
waitpid(pid,NULL,0);
}
}
return 0;
}
Normally, if execlp succeeds, your child process becomes the new process, and thus execution of your code effectively stops at that point.
Now the execlp failed, you print out the error, and continue the child process as if nothing happened! You should exit(0) the child, if the execlp failed.
execlp will fail if cmd does not exist.
As mvds rightly said, when execlp fails, you should exit the child process. But I recommend not forking when cmd does not exist.
You could use something like access (see access(2)) before using fork to make sure cmd exists (within your PATH), and is an executable.
Just post the debug version to make it clear:strncmp is called in which process,and when child or parent exit
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define LINE_LEN 80
int main(int argc,char* argv[])
{
pid_t pid;
char cmd[LINE_LEN+1]={'\0'};
while(1)
{
printf("%s>>",argv[0]);
if(fgets(cmd,sizeof(cmd),stdin)==NULL)
{
perror("fgets failed");
break;
}
cmd[strlen(cmd)-1]='\0';
if(strncmp(cmd,"quit",4)==0)
{
printf("process :%d in strncmp equal\n",getpid());
break;
}
if((pid=fork())==-1)
{
perror("fork failed");
break;
}else if(pid==0)
{
printf("new child:%d\n",getpid());
//TODO no option can be specified for cmd
execlp(cmd,cmd,NULL);
perror("execlp failed");
//This is critical
//exit(0);
}else
{
printf("parent:%d(his child is %d)\n",getpid(),pid);
waitpid(pid,NULL,0);
}
}
printf("process :%d exit...\n",getpid());
return 0;
}
just see one case:
./shell
./shell>>abc
parent:8356(his child is 8357)
new child:8357
execlp failed: No such file or directory
./shell>>quit
process :8357 in strncmp equal
process :8357 exit...
./shell>>quit
process :8356 in strncmp equal
process :8356 exit...

Resources