I am working on writing a custom shell for a school project, and I need to be able to run external commands via the "execv" function. I need my command to either run successfully with the appropriate output, or state that the command was not found. Here is my code (with some printf() output for debugging) at this point:
/* Create a child process */
pid_t pid = fork();
/* Check if the fork failed */
if (pid >= 0)
{
if (pid == 0)
{
/* This is the child process - see if we need to search for the PATH */
if( strchr( command.args[0], '/' ) == NULL )
{
/* Search the PATH for the program to run */
char fullpath[ sizeof( getenv("PATH") ) ];
strcpy( fullpath, getenv("PATH") );
/* Iterate through all the paths to find the appropriate program */
char* path;
path = strtok( fullpath, colon );
while(path != NULL)
{
char progpath[COMMAND_SIZE];
/* Try the next path */
path = strtok( NULL, colon );
strcpy(progpath, path);
strcat(progpath, "/");
strcat(progpath, command.args[0]);
/* Determine if the command exists */
struct stat st;
if(stat(progpath, &st) == 0)
{
/* File exists. Set the flag and break. */
execv( progpath, command.args );
exit(0);
}
else
{
printf("Not found!\n");
}
}
printf("%s: Command not found!\n", command.args[0]);
}
else
{
...
}
/* Exit the process */
exit(EXIT_FAILURE);
}
else
{
/* This is the parent process - wait for the child command to exit */
waitpid( pid, NULL, 0 );
printf("Done with fork!\n");
}
}
else
{
/* Could not fork! */
printf("%s: %s > Failed to fork command!\n", command.args[0], strerror(errno) );
}
And here is the output:
john#myshell:/home/john/project>dir
/usr/local/sbin/dir: Not found!
/usr/local/bin/dir: Not found!
/usr/sbin/dir: Not found!
/usr/bin/dir: Not found!
/sbin/dir: Not found!
/bin/dir: Found!
makefile makefile~ myshell.c myshell.c~ myshell.x
Done with fork!
john#myshell:/home/john/project>foo
/usr/local/sbin/foo: Not found!
/usr/local/bin/foo: Not found!
/usr/sbin/foo: Not found!
/usr/bin/foo: Not found!
/sbin/foo: Not found!
/bin/foo: Not found!
/usr/games/foo: Not found!
Done with fork!
john#myshell:/home/john/project>
The known command "dir" is being found and executed properly. The output is great. However, when I use the fake "foo" command, I expected it to not find the command (which is clearly doesn't), complete the "while" loop, and execute the following "printf" command. This being said, I expected to see the following near the end of the output:
foo: Command not found!
I have tried using a boolean and integer value as a "flag" to determine if the command was found. However, no code seems to run outside the while loop at all. If I remove the "exit(0)", the "printf" command still doesn't run. I am stuck and baffled as to why the code outside the while loop doesn't seem to run at all. I also don't know if this is a problem with the way I am forking or if this has to do with the output buffer.
Am I doing this the wrong way, or how can I ensure that the "Command not found" message always runs exactly one time if the command was not found?
There's an error in your code -- you are using strcpy() and causing a buffer overrun:
// Note the declaration of getenv():
char *getenv(const char *name);
Therefore sizeof(getenv("PATH")) == sizeof(char*), which is probably 4 or 8.
/* Search the PATH for the program to run */
char fullpath[ sizeof( getenv("PATH") ) ]; // allocate fullpath[4] or [8]
strcpy(fullpath, getenv("PATH")); // overrun... copy to 4-8 char stack buffer
// UNDEFINED behavior after this - Bad Things ahead.
You could use malloc() instead to allocate fullpath on the heap dynamically:
char* fullpath = malloc(strlen(getenv("PATH")) + 1); // +1 for terminating NUL
strcpy(fullpath, getenv("PATH")); // OK, buffer is allocated large enough
// ... use fullpath ...
// Then when you are done, free the allocated memory.
free(fullpath);
// And as a general habit you want to clear the pointer after freeing
// the memory to prevent hard-to-debug use-after-free bugs.
fullpath = 0;
Related
I want to use the "base64" script of linux to encode the data and get it in C.
When I try to compile
char a[200];
strcpy(a, "Hello");
printf("%s", a);
I get the output
Hello
Now whenever I try the code
char a[200];
strcpy(a, system("echo Hello | base64"));
printf("%s", a);
I get the output
aGVsbG8K
Segmentation fault
Even when I remove the "printf" statement, I get the same
aGVsbG8K
Segmentation fault
I want to save the value of the output of
system("echo Hello | base64")
in 'a' and not display it. Please help
If you read the documentation for system you'll discover that it doesn't return a string - it's defined as:
int system(const char *command);
The return value is the return status of the command or -1 if there's an error. You can't get the output using system - the output of the command(s) you run will go straight to stdout.
To get the output from another command you could use something like popen.
FILE *myfile;
char buffer[1024];
myfile=popen("echo Hello | base64","r");
if(myfile)
{
while(fgets(buffer,1024,myfile))
{
printf("%s",buffer);
}
pclose(myfile);
}
Here
strcpy(a, system("echo Hello | base64"));
system() doesn't stores it's result into array a as system() job is to execute the command provided in the argument & print it on console i.e stdout buffer. From the manual page of system
system() executes a command specified in command by calling
/bin/sh -c
command, and returns after the command has been completed.
There is one way to solve the problem i.e instead of printing system() output on stdout you can redirect its output to a file & then read that from file & print. For example
int main(void) {
close(1); /* stdout file descriptor is avilable now */
/* create the file if doesn't exist, if exist truncate the content to 0 length */
int fd = open("data.txt",O_CREAT|O_TRUNC|O_RDWR,0664); /* fd gets assigned with lowest
available fd i.e 1 i.e nowonwards stdout output
gets rediredcted to file */
if(fd == -1) {
/* #TODO error handling */
return 0;
}
system("echo Hello | base64"); /* system output gets stored in file */
int max_char = lseek(fd,0,2);/* make fd to point to end, get the max no of char */
char *a = malloc(max_char + 1); /* to avoid buffer overflow or
underflow, allocate memory only equal to the max no of char in file */
if(a == NULL) {
/* #TODO error handling if malloc fails */
return 0;
}
lseek(fd,0,0);/* from beginning of file */
int ret = read(fd,a,max_char);/* now read out put of system() from
file as array and print it */
if(ret == -1) {
/* #TODO error handling */
return 0;
}
a[ret] = '\0';/* \0 terminated array */
dup2(0,fd);/*fd 0 duplicated to file descriptor where fd points i.e */
printf("output : %s \n", a);
/* to avoid memory leak, free the dynamic memory */
free(a);
return 0;
}
My above suggestion is a temporary fix & I won't recommend this, instead use [popen] as suggested by #chris Turner (http://man7.org/linux/man-pages/man3/popen.3.html) which says
The popen() function opens a process by creating a pipe, forking,
and
invoking the shell. Since a pipe is by definition unidirectional, the
type argument may specify only reading or writing, not both; the
resulting stream is correspondingly read-only or write-only.
For example
int main(void) {
char buf[1024];
FILE *fp = popen("echo Hello | base64","r");
printf("%s\n",fgets(buf,sizeof(buf),fp));
return 0;
}
I try to execute a perl script from my C program using a exec family function.
The first try with execv give me an infinite loop, so i check the man page for execv and see that all this function call execve which is more suitable for what i want to do.
Giving up to know why I have an infinite loop (yeah, that's not clever, i know), I intend to use execve, but this one fail too with "No such file or directory".
So i check again the man page, see that
[...]
filename must be either a binary executable, or a script starting with a line of the form:
#! interpreter [optional-arg]
For details of the latter case, see "Interpreter scripts" below.
[...]
ENOENT
The file filename or a script or ELF interpreter does not exist, or a
shared library needed for file or interpreter cannot be found.
So I write a code to check if both my perl scrip and the interpreter exist.
#define PERL_FILE "/home/path/script.pl"
int main(int argc, char *argv[], char *env[])
{
FILE *test;
char buffer[200];
/* Test if PERL_FILE exist */
if (!(test = fopen(PERL_FILE, "r"))) {
fprintf(stderr, "main : fopen("PERL_FILE") : %s\n", strerror(errno));
return (EXIT_FAILURE);
}
/* Retrieve the beginning of the PERL_FILE file */
if (fread(buffer, 1, sizeof(buffer), test) <= 0) {
fprintf(stderr, "main : fread : %s\n", strerror(errno));
return (EXIT_FAILURE);
}
fclose(test);
/* I want to check if the file in the shebang exist */
*strchr(buffer, '\r') = '\0';
printf("shebang = |%s|\n", buffer + 2);
if (!(test = fopen(buffer + 2, "r"))) {
fprintf(stderr, "main : fopen(%s) : %s\n", buffer + 2, strerror(errno));
return (EXIT_FAILURE);
}
fclose(test);
char *argv2[] = {PERL_FILE, NULL};
int i;
for (i = 0; env[i]; ++i) {
printf("%s\n", env[i]);
}
execve(PERL_FILE, argv2, env);
perror ("And so ... ");
return (EXIT_FAILURE);
}
Here the content of my perl script :
#!/usr/bin/perl
print "Hello world !\n";
and here the result of the code execution :
shebang = |/usr/bin/perl|
[...]
USER=root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SHELL=/bin/bash
[...]
And so ... : No such file or directory
(I edited in order to clean my env).
So all this lead to my question : what I'm doing wrong ?
Can't I execute a Perl script with a exec function family ?
I added a space between the shebang and the interpreter (like "#! /usr/bin/perl") but that fail too.
I succed with "system", but i musn't use it.
Thank for your reading.
My code is really messy right now, so I think it would be easier to convey what I'm trying to do by just describing it.
I'm working on a shell for homework that needs to be able to redirect output to a file, just like the default shell. We were provided a preexisting shell and asked to modify it. The shell already sets up an argv for execve, but in order to implement redirection I need to remove the last two entries from the argv the program built (> and the file name), and after testing that by just freeing up the last two entries I think it's probably better to just make a copy, minus those two entries, as this program handles freeing up the argv and if I try to do that at some point before the predesignated time to do so I run into problems when I try to run another command.
My point is I'm having a hard time copying part of an array of strings that's going to serve as an argv. I've seen a couple of solutions posted, but they're all in C++, and I'm asked to do this in C.
Alternatively, I suppose it would also be sufficient if I could properly empty part of an argv. Here's the code I tried:
for(i=0;i<10;i++)
{
if(my_argv[i] == NULL)
{
break;
}
if(strcmp(my_argv[i], ">") == 0)
{
if(my_argv[i+1] != NULL)
{
strncpy(fileName, my_argv[i+1], strlen(my_argv[i+1]));
strncat(fileName, "\0", 1);
//bzero(my_argv[i+1], strlen(my_argv[i+1])+1);
//my_argv[i+1] = NULL;
//free(my_argv[i+1]);
} else {
printf("no file name given\n");
return;
}
//bzero(my_argv[i], strlen(my_argv[i])+1);
//my_argv[i] = NULL;
//free(my_argv[i]);
redirectOutput(cmd, fileName);
return;
}
}
The commented sections are where I copied in code from the function that empties my_argv to attempt to free up the contents of argv where the > and file name are. It still runs without those lines, but then I kick the can down the road with having to deal with the extra entries in my redirectOutput() function, which is an absolute train wreck. The free_argv() function looks like this:
void free_argv()
{
int index;
for(index=0;my_argv[index]!=NULL;index++) {
bzero(my_argv[index], strlen(my_argv[index])+1);
my_argv[index] = NULL;
free(my_argv[index]);
}
}
You're going to much more work than you need to do. You can safely do whatever you want to the pre-prepared argv, as long as you do it after the fork(), in the child (before the execve(), of course). No such modifications will affect the parent, and you don't need to worry about any cleanup because the exec replaces the old process image with the new one.
Example:
/* these are already provided: */
char *filename = /* ... */;
char **child_argv = /* ... */;
char **child_env = /* ... */;
pid_t pid = fork();
if (pid == 0) {
/* the child */
char **arg;
for (arg = child_argv; *arg && strcmp(*arg, ">"); arg += 1) { /* empty */ }
*arg = NULL; /* terminate the arg list at the ">", if present */
/* no need to clean up anything before execve() */
execve(filename, child_argv, child_env);
exit(1); /* execve() failed */
} else if (pid < 0) {
/* handle error */
}
/* the parent continues ... */
I have the following c code which I took from the first answer to this question, I am compiling it with C89 in VS2008 so I made a couple of changes for the code to work it properly, it compiles just fine, but it's not able to create process after creating the namedpipe successfully (CreateProcessA is not working) returning always error 2 and printing the message in the panic function.
The program that I am trying to run in the CreateProcessA can be downloaded here, and I normally run it and use it as follows:
> C:\qhichwa>cmd.exe /c "C:\qhichwa\flookup.exe -bx C:\qhichwa\qhichwa.fst"
wasi <user writes wasi>
wasi <program responds printing wasi>
hola <user writes hola>
+ ? <program responds printing + ?>
<pres ctrl + c to terminate program>
> C:\qhichwa>
The lines between < comments > are just comments.
what are the corrections needed in order to to create the named pipe successfully?
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
// name of our glorious pipe
#define PIPE_NAME L"\\\\.\\pipe\\whatever" // bloody unicode string
// exit on fatal error
void panic(const char * msg)
{
int err = GetLastError();
fprintf(stderr, "***PANIC*** %s\n", msg);
printf("In the child thread: Last Error is %lu\n", err);
exit(-1);
}
// father process
void father(const char * own_name) // name of our own executable to launch a copy of ourselve
{
printf("Father process starting\n");
// create a monodirectional father->child named pipe
HANDLE pipe = CreateNamedPipe(
PIPE_NAME, // name of the pipe
PIPE_ACCESS_OUTBOUND, // send only
PIPE_TYPE_BYTE, // send data as a byte stream
1, // only one instance
0, 0, 0, NULL); // default junk
if (pipe == INVALID_HANDLE_VALUE) panic("could not create pipe 1");
// spawn child process
{
STARTUPINFOA si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcessA( // using ASCII variant to be compatible with argv
"cmd.exe", // executable name (ourself)
"/c \"C:\\qhichwa\\flookup.exe -bx C:\\qhichwa\\qhichwa.fst\"", // command line. This will be seen as argv[0]
NULL, NULL, FALSE, // default junk
CREATE_NEW_CONSOLE, // launch in another console window
NULL, NULL, // more junk
&si, &pi)) // final useless junk
panic("could not create child process 2");
}
// connect to child process
BOOL result = ConnectNamedPipe(pipe, NULL);
if (!result) panic("could not connect to child process");
// talk to child
for (;;)
{
// read an input line
char line[100];
printf("Say something >");
if (fgets(line, sizeof(line), stdin) == NULL)
panic("could not read from standard input");
// exit on an empty line
if (!strcmp(line, "\n")) break;
// send the line to the child
DWORD written = 0;
if (!WriteFile(
pipe,
line, // sent data
strlen(line), // data length
&written, // bytes actually written
NULL))
panic("could not write to pipe");
}
// close the pipe
CloseHandle(pipe);
}
void child(void)
{
printf("Child process starting\n");
// retrieve communication pipe
HANDLE pipe = CreateFile(
PIPE_NAME, // name of the pipe
GENERIC_READ, // read ONLY access (or else the call will fail)
0, NULL, // junk
OPEN_EXISTING, // opens existing pipe
0, NULL); // more junk
if (pipe == INVALID_HANDLE_VALUE) panic("could not connect to the pipe");
// read father's input
for (;;)
{
char buffer[80];
DWORD read = 0;
if (!ReadFile(
pipe,
buffer, // read data
sizeof(buffer)-1, // max length (leave room for terminator)
&read, // bytes actually read
NULL))
break; // exit if the pipe has closed
// display what our father said
buffer[read] = '\0'; // make sure what we just read will be displayable as a string
printf("Father said: %s", buffer);
}
// close pipe
CloseHandle(pipe);
}
int main(int argc, char *argv[])
{
// wait for a <return> keypress on exit
atexit(getchar);
father(argv[0]);
// decide whether we are the father or the child
//if (!strcmp(argv[0], "child")) child();
//else father(argv[0]);
printf("Done\n");
return 0;
}
The problem is located here:
fprintf(stderr, "***PANIC*** %s\n", msg);
printf("In the child thread: Last Error is %lu\n", GetLastError());
This is a standard Windows programming bug, every programmer makes this mistake once. Just once, this is so hard to diagnose that you'll never forget losing the week of your life trying the discover it.
The underlying issue is the way GetLastError() works. It returns the value of an internal global variable, it is stored in the TEB (Thread Environment Block). It has the so common problem with global variables, every call you make to a winapi function is liable to change it, including ones that don't actually fail. Windows itself uses the variable as well. And ERROR_INVALID_NAME is a very popular internal error code.
This is compounded by you not being able to see these winapi calls being made. What is ruining the error code is fprintf(). It is implemented in the CRT with winapi calls. Necessarily so, I/O is an operating system duty.
So what is absolutely essential is that you immediately obtain the error code, before you do anything else. While it is preferable that you pass the value to your panic() function, since it cannot predict what other code runs before it, the quick fix is to rewrite it like this:
int err = GetLastError();
fprintf(stderr, "***PANIC*** %s\n", msg);
printf("In the child thread: Last Error is %lu\n", err);
You'll now get the true error code, the one produced by CreateNamedPipe(). Ought to give you a much better shot at diagnosing the problem. Update your question if you still have a problem interpreting it.
I need to implement a basic shell in C.
One of things I need is to implement a function that has a command and to execute it.
my code:
pID=fork();
if (pID == 0)
execvp(tmp[0], tmp);
else if (pID > 0)
{
printf("%d", pID);
wait(NULL);
}
else
printf("Failed to create proccess \n");
The problem is that no matter what is the command I put in tmp, the program shows me the prompt again, and do nothing except that.
For example if I write gedit (in order to open the gedit — a ntpad of Ubuntu), it doesn't open it, or if write ls -a it doesn't show me any output as the terminal of Ubuntu does.
execvp should work. As the others mentioned, you really need to show how you populate tmp. That said, I would guess that that's where the error is. tmp needs to be a null terminated array.
#include <stdio.h>
main( int argc, char * argv[] )
{
int pid = fork;
char * tmp[2];
memset( tmp, 0, sizeof(tmp) );
tmp[0] = argv[0];
if( 0 == pid )
{
if( -1 == execvp( tmp[0], tmp ) )
{
char errmsg[64];
snprintf( errmsg, sizeof(errmsg), "exec '%s' failed", tmp[0] );
perror( errmsg );
}
else if( 0 < pid )
{
printf("[%d] %s\n", pid, tmp[0]);
wait(NULL);
}
else
{
perror("fork failed");
}
}
Although you've failed to tell us what you're passing through the tmp variable to execvp, my psychic sense tells me that you forgot to null-terminate your argument list. A NULL argument tells execvp where the last argument is, and if you fail to put in a NULL, it will start reading random garbage off the stack.
If that random garbage points to large strings of non-zero data, it will run out of space to store the supposed arguments to the new process, which is typically a few hundred KB (see this page for some system-specific numbers, as well as various ways of getting your system's maximum arguments size).
When there's too much argument data, the system call execve(2) (called internally by execvp) fails with the error E2BIG.
So to see if this is what's happening to you, check the return value from execvp. If it even returns at all, it failed (if it succeeded, it wouldn't have returned since a new process would be executing!), so check the global value of errno to see why it failed:
if (pID == 0)
{
execvp(tmp[0], tmp);
printf("exec failed: %s\n", strerror(errno));
exit(1);
}
execvp() requires full path . If in tmp[0] isnt the full path of your executable file use execv()
execv(tmp[0], tmp);