In C, how can I find out programmatically if a process is already running on Linux/Ubuntu to avoid having it start twice? I'm looking for something similar to pidof.
You can walk the pid entries in /proc and check for your process in either the cmdline file or perform a readlink on the exe link (The following uses the first method).
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
pid_t proc_find(const char* name)
{
DIR* dir;
struct dirent* ent;
char* endptr;
char buf[512];
if (!(dir = opendir("/proc"))) {
perror("can't open /proc");
return -1;
}
while((ent = readdir(dir)) != NULL) {
/* if endptr is not a null character, the directory is not
* entirely numeric, so ignore it */
long lpid = strtol(ent->d_name, &endptr, 10);
if (*endptr != '\0') {
continue;
}
/* try to open the cmdline file */
snprintf(buf, sizeof(buf), "/proc/%ld/cmdline", lpid);
FILE* fp = fopen(buf, "r");
if (fp) {
if (fgets(buf, sizeof(buf), fp) != NULL) {
/* check the first token in the file, the program name */
char* first = strtok(buf, " ");
if (!strcmp(first, name)) {
fclose(fp);
closedir(dir);
return (pid_t)lpid;
}
}
fclose(fp);
}
}
closedir(dir);
return -1;
}
int main(int argc, char* argv[])
{
if (argc == 1) {
fprintf("usage: %s name1 name2 ...\n", argv[0]);
return 1;
}
int i;
for(int i = 1; i < argc; ++i) {
pid_t pid = proc_find(argv[i]);
if (pid == -1) {
printf("%s: not found\n", argv[i]);
} else {
printf("%s: %d\n", argv[i], pid);
}
}
return 0;
}
This is the same as the code posted by John Ledbetter . It is good to refer to the file named stat in /proc/pid/ directory than cmdline since the former gives process states and process name. The cmdline file gives complete arguments with which the process is started. So that fails in some cases. Any way the idea given by John is good. Here I posted the modified code of John. I was looking for the code in c in Linux to check dhcp is running or not . With this code, I am able to do that. I hope it may be useful for someone like me.
#include <sys/types.h>
#include <dirent.h>
#include<unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
pid_t proc_find(const char* name)
{
DIR* dir;
struct dirent* ent;
char buf[512];
long pid;
char pname[100] = {0,};
char state;
FILE *fp=NULL;
if (!(dir = opendir("/proc"))) {
perror("can't open /proc");
return -1;
}
while((ent = readdir(dir)) != NULL) {
long lpid = atol(ent->d_name);
if(lpid < 0)
continue;
snprintf(buf, sizeof(buf), "/proc/%ld/stat", lpid);
fp = fopen(buf, "r");
if (fp) {
if ( (fscanf(fp, "%ld (%[^)]) %c", &pid, pname, &state)) != 3 ){
printf("fscanf failed \n");
fclose(fp);
closedir(dir);
return -1;
}
if (!strcmp(pname, name)) {
fclose(fp);
closedir(dir);
return (pid_t)lpid;
}
fclose(fp);
}
}
closedir(dir);
return -1;
}
int main(int argc, char* argv[])
{
int i;
if (argc == 1) {
printf("usage: %s name1 name2 ...\n", argv[0]);
return 1;
}
for( i = 1; i < argc; ++i) {
pid_t pid = proc_find(argv[i]);
if (pid == -1) {
printf("%s: not found\n", argv[i]);
} else {
printf("%s: %d\n", argv[i], pid);
}
}
return 0;
}
There are ways to avoid /proc usage (and there might be good reasons to do so, e.g. /proc might not be installed at all, and/or it might have been symlinked to something deceptive, or that pid has been hidden in /proc). Granted, the below method doesn't look that good, I wish there were a proper API for that!
Anyway, section 1.9 of a 1997 Unix programming FAQ says:
Use kill() with 0 for the signal number. There are four possible results from this call:
kill() returns 0
This implies that a process exists with the given PID, and the
system would allow you to send signals to it. It is
system-dependent whether the process could be a zombie.
kill() returns -1, errno == ESRCH
Either no process exists with the given PID, or security
enhancements are causing the system to deny its existence. (On
some systems, the process could be a zombie.)
kill() returns -1, errno == EPERM
The system would not allow you to kill the specified process.
This means that either the process exists (again, it could be a
zombie) or draconian security enhancements are present (e.g. your
process is not allowed to send signals to anybody).
kill() returns -1, with some other value of errno
You are in trouble!
The most-used technique is to assume that success or failure with EPERM
implies that the process exists, and any other error implies that it
doesn't.
pidof works by walking over the /proc filesystem. In C, you could do something similar by enumerating /proc; opening /proc/X/cmdline for every X where X is a list of one or more decimal numbers. I don't know if you have any portability requirements but bear that in mind if you are to rely on the availability of /proc.
This problem is more commonly solved on UNIX-like systems by wrapping the start-up of the program and maintaining a PID file. See /etc/init.d/* for classic examples of this approach. You will need to be careful to ensure that the code which reads of writes the PID file does so in a safe manner (atomically). If your target OS has a more capable init (such as systemd), you may be able to out source this work to that.
Related
I am now taking a security class. And the professor asked us to exploit a program to gain higher privilege.
This is the code
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#define BUFSIZE 512
int main(int argc, char *argv[]) {
struct stat buf;
char cmd[BUFSIZE];
FILE *f = NULL;
if (argv[1] == NULL) {
fprintf(stderr, "Please provide an argument\n");
exit(1);
}
if (stat(argv[1], &buf)) {
fprintf(stderr, "Can't stat the file\n");
exit(1);
}
if (buf.st_gid != getegid()) {
fprintf(stderr, "The file must be owned by group %d\n", getegid());
exit(1);
}
sleep(1);
if ((f = fopen(argv[1], "r")) == NULL) {
fprintf(stderr, "Cannot open command file\n");
return 1;
}
while (fgets(cmd, BUFSIZE, f)) {
if ((cmd[0] == '\n') || (cmd[0] == 0x7f)) {
fprintf(stderr, "Found empty line, quitting!\n");
return 2;
}
system(cmd);
}
printf("Done!\n");
return 0;
}
The particular situation is:
There is a binary code for this program for us to run. And the SUID of the binary code is set to higher level(here I assume level2). However, buf.st_gid != getegid() will always check if the file we specified is owned by root. Most important thing is, I only have level1 privilege. There is only one file owned by level2 I can use which is provided by our professor. And I certainly tried to pass in that filename. But nothing happens. I don't know if there is anyway I can exploit this program.(To execute some commands during runtime using the privilege of level2)
I wrote a shell for an assignment and it works correctly, but there is a small run time error which i can not figure out. When the user enter the command 'exit' through the shell it should come out of newly created shell. But the problem is I have to type the command 'exit' several times to quit the shell. If someone can help me it will be a great pleasure for me! Thanks all!
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
char* cmndtkn[256];
char buffer[256];
char* path=NULL;
char pwd[128];
int main(){
//setting path variable
char *env;
env=getenv("PATH");
putenv(env);
system("clear");
printf("\t MY OWN SHELL !!!!!!!!!!\n ");
printf("_______________________________________\n\n");
while(1){
fflush(stdin);
getcwd(pwd,128);
printf("[MOSH~%s]$",pwd);
fgets(buffer,sizeof(buffer),stdin);
buffer[sizeof(buffer)-1] = '\0';
//tokenize the input command line
char* tkn = strtok(buffer," \t\n");
int i=0;
int indictr=0;
// loop for every part of the command
while(tkn!=NULL)
{
if(strcoll(tkn,"exit")==0 ){
exit(0);
}
else if(strcoll(buffer,"cd")==0){
path = buffer;
chdir(path+=3);}
else if(strcoll(tkn,"|")==0){
indictr=i;}
cmndtkn[i++] = tkn;
tkn = strtok(NULL," \t\n");
}cmndtkn[i]='\0';
// execute when command has pipe. when | command is found indictr is greater than 0.
if(indictr>0){
char* leftcmnd[indictr+1];
char* rightcmnd[i-indictr];
int a,b;
for(b=0;b<indictr;b++)
leftcmnd[b]=cmndtkn[b];
leftcmnd[indictr]=NULL;
for(a=0;a<i-indictr-1;a++)
rightcmnd[a]=cmndtkn[a+indictr+1];
rightcmnd[i-indictr]=NULL;
if(!fork())
{
fflush(stdout);
int pfds[2];
pipe(pfds);
if(!fork()){
close(1);
dup(pfds[1]);
close(pfds[0]);
execvp(leftcmnd[0],leftcmnd);
}
else{
close(0);
dup(pfds[0]);
close(pfds[1]);
execvp(rightcmnd[0],rightcmnd);
}
}else wait(NULL);
//command not include pipe
}else{
if(!fork()){
fflush(stdout);
execvp(cmndtkn[0],cmndtkn);
}else wait(NULL);
}
}
}
Like the cd command, the exit command has to be interpreted by the shell as a built-in; it must exit the loop or call the exit() function directly. However, it also appears that should be happening. Note that using strcoll() is a little unusual; normally, strcmp() is sufficient.
You should report problems if execvp() returns — and you must make sure the sub-shell exits so that you don't have multiple shell processes reading the input simultaneously. I'm left wondering if this problem is occurring, and that's why you have to type exit multiple times.
You also need to check that fgets() did not report an error. It always null terminates its input; your code does not zap the newline (you'd need strlen(buffer)-1 instead of sizeof(buffer)-1).
The code that reads and sets PATH is wrong. getenv("PATH") returns a pointer to the first character after the PATH= part; you then use that to 'set' the environment. Fortunately for you, the average value for PATH does not contain anything that looks like VAR=value, so it is functionally a no-op (though the information is probably copied into the environment, where it makes a mess without causing any major harm).
Your code indentation scheme is rococo at best — mostly, it is just woefully inconsistent. Please be consistent! The spacing of the lines in the code was also extremely erratic. When you're adding code in SO, do not use tabs, do use 4 spaces per indent level, do highlight a block of code that is left justified and use the {} button above the edit box to indent it as code. This also means you don't need to add blank lines to the code.
You aren't closing enough file descriptors. When you use dup() (or dup2()) to duplicate a pipe to standard input or standard output, you have to close both of the file descriptors returned by pipe().
On Linux, using fflush(stdin) is undefined behaviour, AFAIK. It is defined on Windows, but not on POSIX systems.
You don't check that your chdir() system call works.
Trying your code, I did get one runaway prompt. Unfortunately, I couldn't remember or see what triggered the runaway. The code below is mostly sanitized and seems to behave. I've annotated some critical changes — and not others. One of the things you should be doing for your own benefit is including trace like the dump_cmd() function so you can see what your program is doing.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
char *cmndtkn[256];
char buffer[256];
char *path = NULL;
char pwd[128];
static void dump_cmd(char **argv);
int main(void)
{
/*
//setting path variable
char *env;
env=getenv("PATH");
putenv(env);
system("clear");
*/
printf("\t MY OWN SHELL !!!!!!!!!!\n ");
printf("_______________________________________\n\n");
while (1)
{
//fflush(stdin);
getcwd(pwd, 128);
printf("[MOSH~%s]$", pwd);
if (fgets(buffer, sizeof(buffer), stdin) == 0)
{
putchar('\n');
break;
}
//buffer[sizeof(buffer)-1] = '\0';
buffer[strlen(buffer)-1] = '\0';
//tokenize the input command line
char *tkn = strtok(buffer, " \t\n");
int i = 0;
int indictr = 0;
// loop for every part of the command
while (tkn != NULL)
{
if (strcoll(tkn, "exit") == 0)
{
printf("Got: exit\n");
fflush(stdout);
exit(0);
}
else if (strcoll(tkn, "cd") == 0) // Was buffer, not tkn
{
printf("Got: cd (%s)\n", buffer + 3);
fflush(stdout);
path = buffer;
chdir(path += 3);
}
else if (strcoll(tkn, "|") == 0)
{
indictr = i;
}
cmndtkn[i++] = tkn;
tkn = strtok(NULL, " \t\n");
}
cmndtkn[i] = 0;
// execute when command has pipe. when | command is found indictr is greater than 0.
if (indictr > 0)
{
char *leftcmnd[indictr+1];
char *rightcmnd[i-indictr];
int a, b;
for (b = 0; b < indictr; b++)
leftcmnd[b] = cmndtkn[b];
leftcmnd[indictr] = NULL;
for (a = 0; a < i-indictr-1; a++)
rightcmnd[a] = cmndtkn[a+indictr+1];
rightcmnd[i-indictr-1] = NULL; // Did not include -1
if (!fork())
{
fflush(stdout);
int pfds[2];
pipe(pfds);
if (!fork())
{
dump_cmd(leftcmnd);
close(1);
dup(pfds[1]);
close(pfds[0]);
close(pfds[1]);
execvp(leftcmnd[0], leftcmnd);
fprintf(stderr, "failed to execvp() %s\n", leftcmnd[0]);
exit(1);
}
else
{
dump_cmd(rightcmnd);
close(0);
dup(pfds[0]);
close(pfds[0]);
close(pfds[1]);
execvp(rightcmnd[0], rightcmnd);
fprintf(stderr, "failed to execvp() %s\n", rightcmnd[0]);
exit(1);
}
}
else
wait(NULL);
}
else
{
//command does not include pipe
if (!fork())
{
dump_cmd(cmndtkn);
fflush(stdout);
execvp(cmndtkn[0], cmndtkn);
fprintf(stderr, "failed to execvp() %s\n", cmndtkn[0]);
exit(1);
}
else
wait(NULL);
}
}
return 0;
}
static void dump_cmd(char **argv)
{
int i = 0;
fprintf(stderr, "%d: Command:\n", (int)getpid());
while (*argv != 0)
fprintf(stderr, "%d: %d: [[%s]]\n", (int)getpid(), i++, *argv++);
}
I'm not keen on the code, but it does seem mostly sane.
Hi I need a little help with parallel download program.
Currently, it is downloading the same file in parallel instead of downloading multiple files at the same time.
Something is wrong with the fork and fgets, not sure how to fix them. Thank you.
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
FILE *file; /*declare the file pointer*/
#define LINE_MAX 1000
char line [LINE_MAX];
//Parent process
int main()
{
pid_t pid;
file= fopen ("urls.txt", "rt"); /*open file and read it*/
if(!file)
{
perror("fopen");
exit(-1);
}
int numberOfChildren = 0;
while (!feof (file)) {
memset (line,'\0',1000);
char *urlPtr;
while (!feof (file))
{
urlPtr= fgets (line,LINE_MAX, file);
if(urlPtr)
{
int lineLen = strlen(urlPtr);
urlPtr[lineLen-1] = '\0';
pid = fork();
++numberOfChildren;
if (pid == 0) { /* child process */
execlp("/usr/bin/wget", "wget", urlPtr, NULL);
}
else if (pid < 0) { /* error occurred */
fprintf(stderr, "Fork Failed");
exit(-1);
}
}
}
while (numberOfChildren>0) { /* parent process */
/* parent will wait for the child to complete */
wait (NULL);
--numberOfChildren;
printf ("Child Complete");
}
}
fclose (file); /*close file command*/
return 0;
}
You have the fork() check outside the URL reading loop. You first read lots of URLs and spawn a lot of children, and then do the pid check. Try
while (!feof (file))
{
urlPtr= fgets (line,LINE_MAX, file);
pid = fork();
if (pid == 0) { /* child process */
execlp("/usr/bin/wget", "wget", urlPtr, NULL);
}
else if (pid < 0) { /* error occurred */
fprintf(stderr, "Fork Failed");
exit(-1);
}
++numberOfChildren;
}
You should put a diagnostic print and exit after the execlp() (but in the child code after the if). You should probably also close the input file before you execute wget; the program doesn't need it open. No huge harm done this time, but it's good to be tidy. Your parent probably shouldn't exit just because one child failed to fork(); you have other children, in general, that you should wait for. You might stop processing the file at that point, though. And you should definitely forget about feof(); use while (fgets(line, sizeof(line), file) != 0), though that means you don't need urlPtr. The memset() is superfluous; fgets() initializes the string correctly.
Adaptation of code in question
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
FILE *file; /*declare the file pointer*/
#define LINE_MAX 1000
char line [LINE_MAX];
//Parent process
int main(void)
{
pid_t pid;
file = fopen("urls.txt", "rt"); /*open file and read it*/
if (!file)
{
perror("fopen");
exit(-1);
}
int numberOfChildren = 0;
memset(line,'\0',1000);
char *urlPtr;
while (!feof(file))
{
urlPtr= fgets(line, sizeof(line), file);
if (urlPtr)
{
int lineLen = strlen(urlPtr);
urlPtr[lineLen-1] = '\0';
pid = fork();
++numberOfChildren;
if (pid == 0)
{ /* child process */
execlp("/usr/bin/wget", "wget", urlPtr, NULL);
fprintf(stderr, "%d: wget failed\n", (int)getpid());
exit(1);
}
else if (pid < 0)
{ /* error occurred */
fprintf(stderr, "Fork Failed\n");
exit(-1);
}
else
printf("%d: %s\n", (int)pid, urlPtr);
}
}
/* JL: Moved block of code */
while (numberOfChildren>0)
{ /* parent process */
/* parent will wait for the child to complete */
int status;
int corpse = wait(&status);
--numberOfChildren;
printf("Child %d Complete (0x%04X)\n", corpse, status);
}
fclose(file); /*close file command*/
return 0;
}
Note that a while (!feof(file)) loop has been removed, but there is more unnecessary code that could go. Given data file
ftp://ftp.iana.org/tz/releases/tzcode2012f.tar.gz
ftp://ftp.iana.org/tz/releases/tzdata2012f.tar.gz
The code above works fetching the two files in parallel.
Alternative code
I like to use functions, even for relatively short stretches of code that are used once. Hence the be_childish() function added below. The error reporting is a bit tedious to write out, but that is no excuse for not doing it.
I briefly introduced a minimal function that does error reporting, based on an elaborate library of my own, but it would only be used twice in this code (for the file open error and after execlp() returns, which always and unconditionally indicates failure), but decided to leave it out. I have functions such as err_setarg0(), err_error(), err_remark() and err_usage() and using those would reduce each error report to a single line (and some more complex functions that could be told to include the PID automatically, etc). To me, it is worth having such a library as it makes error checking much, much simpler and therefore less painful and less likely to be skimped on.
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
static void be_childish(const char *urlPtr)
{
const char *wget = "/usr/bin/wget";
char *nl = strchr(urlPtr, '\n');
if (nl != 0)
*nl = '\0';
printf("%d: %s\n", (int)getpid(), urlPtr);
execlp(wget, "wget", urlPtr, NULL);
fprintf(stderr, "%d: Failed to execute %s\n", (int)getpid(), wget);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv)
{
FILE *file;
char line [1024];
pid_t pid;
const char *name = "urls.txt";
int rc = EXIT_SUCCESS;
if (argc == 2)
name = argv[1];
else if (argc > 2)
{
fprintf(stderr, "Usage: %s [filename]\n", argv[0]);
exit(EXIT_FAILURE);
}
file = fopen(name, "rt"); /* Undefined behaviour per POSIX */
int numberOfChildren = 0;
if (file == 0)
{
fprintf(stderr, "Failed to open file %s\n", name);
exit(EXIT_FAILURE);
}
while (fgets(line, sizeof(line), file) != 0)
{
if ((pid = fork()) == 0)
{
fclose(file);
be_childish(line);
}
else if (pid < 0)
{
fprintf(stderr, "Fork Failed");
rc = EXIT_FAILURE;
break;
}
++numberOfChildren;
}
fclose(file);
/* Parent waits for the children to complete */
while (numberOfChildren > 0)
{
int status;
const char *result = "OK";
pid = wait(&status);
--numberOfChildren;
if (status != 0)
{
result = "Failed";
rc = EXIT_FAILURE;
}
printf("Child %d %s\n", pid, result);
}
return rc;
}
Note that the code takes a file name on the command line, defaulting to your "urls.txt". The "rt" open mode is not a POSIX or standard C mode; it will likely work, but "r" is sufficient to open a text file on all systems ("rb" to open a binary file works on all systems too, and is POSIX and standard C compliant). It reports which child process is processing each file listed. It reports the status (success or failure) of each child; it's own exit status is only success if all the children were successful.
You could probably control the verboseness from the command line. You might also want to keep a record of which child was processing each file so that you could report on files successfully downloaded, rather than on the processes which the user doesn't care about, really. That complicates the processing since you need to make a copy of each URL as you read it.
Note that you do need to trim the newlines off the end of the string (URL) before passing it to wget.
This code now tested (after adding the newline amendment), and it produced two files. The screen display is a bit of a mess; that's because each copy of wget thinks it is the sole user:
80334: ftp://ftp.iana.org/tz/releases/tzcode2012f.tar.gz
80335: ftp://ftp.iana.org/tz/releases/tzdata2012f.tar.gz
--2012-09-23 19:19:44-- ftp://ftp.iana.org/tz/releases/tzcode2012f.tar.gz
=> “tzcode2012f.tar.gz”
Resolving ftp.iana.org... --2012-09-23 19:19:44-- ftp://ftp.iana.org/tz/releases/tzdata2012f.tar.gz
=> “tzdata2012f.tar.gz”
Resolving ftp.iana.org... 192.0.32.8192.0.32.8, , 2620:0:2d0:200::82620:0:2d0:200::8
Connecting to ftp.iana.org|192.0.32.8|:21... Connecting to ftp.iana.org|192.0.32.8|:21... connected.
Logging in as anonymous ... connected.
Logging in as anonymous ... Logged in!
==> SYST ... Logged in!
==> SYST ... done. ==> PWD ... done. ==> PWD ... done.
==> TYPE I ... done.
==> TYPE I ... done. ==> CWD (1) /tz/releases ... done. ==> CWD (1) /tz/releases ... done.
==> SIZE tzdata2012f.tar.gz ... done.
==> SIZE tzcode2012f.tar.gz ... 206404
==> PASV ... 135543
==> PASV ... done. ==> RETR tzdata2012f.tar.gz ... done. ==> RETR tzcode2012f.tar.gz ... done.
Length: 206404 (202K) (unauthoritative)
0% [ ] 0 --.-K/s done.
Length: 135543 (132K) (unauthoritative)
100%[==============================================================================>] 135,543 72.7K/s in 1.8s
100%[==============================================================================>] 206,404 81.4K/s in 2.5s
2012-09-23 19:19:48 (72.7 KB/s) - “tzcode2012f.tar.gz” saved [135543]
Child 80334 OK
2012-09-23 19:19:48 (81.4 KB/s) - “tzdata2012f.tar.gz” saved [206404]
Child 80335 OK
I want to write a program which will create a new process and in that child process, it should execute the command: ls. In the meanwhile, the parent should wait for the child to die. However, my code does not work.
Please help me thank you very much!
int main()
{
char * read;
size_t size;
getline(&read , &size , stdin);
read[strlen(read)-1] = '\0';
printf("%s\n" , read);
int status;
pid_t f;
if((f = fork()) == 0)
{
execvp(read , &read);
exit(0);
}
else
{
wait(&status);
}
}
From man execvp:
The execv(), execvp(), and execvpe() functions provide an array of pointers to null-terminated strings that represent the argument list available to the new program. The first argument, by convention, should point to the filename associated with the file being executed. The array of pointers must be terminated by a NULL pointer.
You need to use an array of char* and set the last element to NULL.
I am unsure what the getline() is reading but I guess it is the directory to be lsd. The first argument to execvp() should be ls and the second argument the array of char*.
Consider the following:
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{
char *input_arg[2];
char *input_str = NULL;
size_t input_len = 0;
char **args;
ssize_t len;
size_t n;
pid_t child, p;
int status;
if (argc < 2) {
/* No command line parameters. Read command from stdin. */
len = getline(&input_str, &input_len, stdin);
/* Find length excluding the newline at end. */
if (len > (ssize_t)0)
n = strcspn(input_str, "\r\n");
else
n = 0;
if (n > (size_t)0) {
/* Terminate input command before the newline. */
input_str[n] = '\0';
} else {
fprintf(stderr, "No input, no command.\n");
return 1;
}
input_arg[0] = input_str;
input_arg[1] = NULL;
args = input_arg;
} else {
/* Use command line parameters */
argv[argc] = NULL;
args = argv + 1;
}
child = fork();
if (child == (pid_t)-1) {
fprintf(stderr, "Cannot fork: %s.\n", strerror(errno));
return 1;
}
if (!child) {
/* This is the child process. */
errno = ENOENT;
execvp(args[0], args);
fprintf(stderr, "%s: %s.\n", args[0], strerror(errno));
exit(127);
}
do {
p = waitpid(child, &status, 0);
} while (p == (pid_t)-1 && errno == EINTR);
if (p == (pid_t)-1) {
fprintf(stderr, "Lost child process: %s.\n", strerror(errno));
return 127;
}
if (p != child) {
fprintf(stderr, "waitpid() library bug occurred.\n");
return 127;
}
if (WIFEXITED(status)) {
if (!WEXITSTATUS(status))
fprintf(stderr, "Command successful.\n");
else
fprintf(stderr, "Command failed with exit status %d.\n", WEXITSTATUS(status));
return WEXITSTATUS(status);
}
if (WIFSIGNALED(status)) {
fprintf(stderr, "Command died by signal %s.\n", strsignal(WTERMSIG(status)));
return 126;
}
fprintf(stderr, "Command died from unknown causes.\n");
return 125;
}
The above uses the command line parameters if specified, otherwise it reads one from the standard input. Because the standard input is not tokenized, you can only supply the command name, no parameters. If you enlarge the input_arg[] array into
char *input_arg[4];
and modify the assignment into
input_arg[0] = "/bin/sh";
input_arg[1] = "-c";
input_arg[2] = input_str;
input_arg[3] = NULL;
args = input_arg;
then the input string will be processed using the /bin/sh shell, just like popen() does.
You can also use len = getdelim(&input_str, &input_len, '\0', stdin); and remove the input_str[n] = '\0'; assignment to allow multiline input; the shell should handle those fine, as long as it is short enough to fit in the command line argument buffer (maximum length depends on your OS).
The rules how shells split input into separate commands and parameters are rather complex, and you should not try to emulate them. Instead, find a simple way for the user to specify the parameters separately (like the command-line parameter case), or use the shell to do it for you. If you don't do any splitting, you will probably need to remove the newline at the end of the input line.
The point to note is that for execvp(file, args), args[0] is the name the application sees (as $0 or argv[0]), and args[1] is the first parameter. Each parameter is terminated by NUL (\0) just like strings are normally in C, and the args pointer array must end with a NULL pointer. If there are no parameters, then args[1] == NULL.
why dont you just use system command...
#include <stdio.h>
#include <stdlib.h>
int main ()
{
int i;
printf ("Executing command ls...\n");
i=system ("ls");
printf ("The value returned was: %d.\n",i);
return 0;
}
Update:
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
void main(void)
{
pid_t pid;
pid = fork();
if (pid == 0) // this is child process
{
int i;
printf ("Executing command ls...\n");
i=system ("ls");
printf ("The value returned was: %d.\n",i);
}
else // this is paraent process
{
int status=0
wait(&status);
printf ("Child process is returned with: %d.\n",status);
}
}
I am trying to learn Unix C and doing some exercises for practice. I am currently working on writing my own shell that works similar to the linux bash shell.
The code I have below provides for a fairly basic shell. It now provides I/O redirection.
I am trying to add support for piping. Initially, I just want to add support for a single pipe.
I have tried to go through some tutorials online but can't quite figure out where to start.
Currently, the shell below can handle commands commands such as the following.
ls > abc, cat< file1 > file2, etc.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <sys/wait.h>
#include <fcntl.h>
#define TRUE 1
int main(void)
{
char *arg_list[10];
int status;
int counter = 0;
int counter2 = 0;
pid_t pid;
char buf[100];
char inFile[10];
char outFile[10];
int fdIn, fdOut;
while(TRUE)
{
printf("> ");
if (!fgets(buf, 100, stdin))
return 0;
pid = fork();
switch(pid)
{
case -1:
return 1;
case 0:
{
arg_list[counter] = strtok(buf, " \n");
counter = 0;
while(arg_list[counter] != NULL)
{
counter++;
arg_list[counter] = strtok(NULL, " \n");
}
counter2 = 0;
while(arg_list[counter2] != NULL)
{
if(!strcmp(arg_list[counter2], "<"))
{
if(arg_list[counter2+1] != NULL)
{
fdIn = open(arg_list[counter2+1], O_RDONLY);
dup2(fdIn, STDIN_FILENO);
}
else
{
printf("No input file specified");
}
arg_list[counter2] = 0;
}
else if(!strcmp(arg_list[counter2], ">"))
{
if(arg_list[counter2+1] != NULL)
{
fdOut = open(arg_list[counter2+1], O_CREAT | O_WRONLY | O_TRUNC, 0666);
dup2(fdOut, STDOUT_FILENO);
}
else
{
printf("No output file specified");
}
arg_list[counter2] = 0;
}
counter2++;
}
execvp(arg_list[0], arg_list);
break;
}
default:
waitpid(-1, &status, 0);
break;
}
}
return 0;
}
If somebody can point me in the right direction, that would be appreciated.
After you use dup2() (successfully), you have two file descriptors open for the one file. You need to close the original file descriptor; the executed process should not have the extra file descriptors open.
You are also going to need to open the files in the appropriate way before using dup2(). Amongst other things, that means strtok() is not a good choice because it blats out the delimiter but you need to know which file to open for reading and which for writing).
You are also going to need to fettle the argument list; it should contain just the command name and a null pointer, not the two file names.
Why do you need to check the type of command? Unix shells don't treat any commands specially; all redirections, including pipes, are handled the same way. One thing to watch out for is that redirections can happen anywhere in a command, so you should parse them out first; try
>foo ls <bar -la
in a shell sometime. (Pipes are an obvious exception to this, since they also delimit commands; syntactically | is the same as ;, although semantically there is redirection involved in addition.)