Getting and Setting the path - c

I've been giving the task of writing a simple shell, I've managed to get some really basic functionality however for one of the stages it's asking me to get and set the environment. This is the details of the task.
Stage 4: Getting and setting the path – 10 marks
Keep original path
The reason this is necessary is because we would like to restore the path to what was originally on exiting the shell. This is important because any changes you do to the shell’s environment (i.e. setenv() function above), does not only affect the shell itself but also any other program that will be executed from the same terminal afterwards. For this reason, it is a good idea to put things back the way they were on exit.
A single string will be enough to keep the original path.
Saving the path should be the first thing your shell does when it starts up.
Print and change the path – built-in commands
From a C program we can access the environment using the getenv() function and we can change the environment using the setenv() function. If you look at the manual pages for setenv(), you will find how it works (i.e. parameters needed and return values) as well as what you need to include to use it.
getpath – print system path & setpath – set system path
These two commands are about the environment parameter PATH. The first just gets and prints its value, while the second takes a path (string of colon separated list of directories) as a parameter and makes it the value of PATH. You can getenv() and setenv() respectively for this purpose.
Restore path
You just change the PATH environment parameter to its original value (i.e. the one you saved at the start up of the shell).
Restoring the path should the last thing your shell does before it exits.
Stage 4: Testing
First, make sure that all the tests you carried out for stage 3 still work. Be careful though, as we are now changing the path this will affect the execution of external programs.
To check the additional functionality, you should start by checking that the save and restore of the path work. A good idea here is to print the path when you save it at the beginning of the execution of the shell and then again when you exit at the end. In both cases the printed path should be exactly the same!
Following that you should check that when getpath is called you print the current path, which should be the same as the original one.
Then you should focus on testing setpath. First, setpath the path to a new value and test that getpath prints it, then try also to see how changing the path really affects the execution of external commands (e.g. set the path to only ‘.’ and try ‘ls’ or try the shell itself, etc).
This is my code:
/* S.NO:201148541 Simple Shell Example
Completed upto Stage 3 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#define BUFFER_SIZE 1<<16
#define ARR_SIZE 1<<16
void parse_args(char *buffer, char** args,
size_t args_size, size_t *nargs)
{
char *buf_args[args_size];
char **cp;
char *wbuf;
size_t i, j;
wbuf=buffer;
buf_args[0]=buffer;
args[0] =buffer;
for(cp=buf_args; (*cp=strsep(&wbuf, " \n\t")) != NULL ;){
if ((*cp != '\0') && (++cp >= &buf_args[args_size]))
break;
}
for (j=i=0; buf_args[i]!=NULL; i++){
if(strlen(buf_args[i])>0)
args[j++]=buf_args[i];
}
*nargs=j;
args[j]=NULL;
}
int main(int argc, char *argv[], char *envp[]){
char buffer[BUFFER_SIZE];
char *args[ARR_SIZE];
int *ret_status;
size_t nargs;
pid_t pid;
char curDir[100];
while(1){
getcwd(curDir, 100);
printf("%s->", curDir);
fgets(buffer, BUFFER_SIZE, stdin);
parse_args(buffer, args, ARR_SIZE, &nargs);
if (nargs==0) continue;
if(strcmp(args[0], "cd") == 0){
chdir(args[1]);
}
else if (!strcmp(args[0], "exit" )) exit(0);
pid = fork();
if (pid){
pid = wait(ret_status);
printf("finished\n", pid);
}
else {
if( execvp(args[0], args)) {
puts(strerror(errno));
exit(127);
}
}
}
}
return 0;
}
I'm really at a loss and any guidance would be helpful.

Given that we don't know what your previous steps are, and going by the advice
Then you should focus on testing setpath. First, setpath the path to a
new value and test that getpath prints it, then try also to see how
changing the path really affects the execution of external commands
(e.g. set the path to only ‘.’ and try ‘ls’ or try the shell itself,
etc).
You can do like this...
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *path, *old, *tobeSet;
path = malloc(1024);
path = getenv("PATH");
old = malloc(strlen(path));
tobeSet = malloc(10); // just to be safe
strcpy(tobeSet,".");
if(setenv("PATH",tobeSet,1)!=0)
{
printf("Couldn't set path\n");
return 0;
}
printf("\nPATH::\t%s\n",path);
printf("\n\nNewPath::\t%s\n",tobeSet);
if(setenv("PATH",path,1)!=0)
{
printf("Couldn't restore path\n");
return 0;
}
printf("\n\nOld path ::\t%s\n",path);
free(path);
free(old);
free(tobeSet);
return 0;
}

Related

Obtain the name of the current directory (not the path)

I had seen, and I had used a couple of time the function cwd() to get the absolute path of a folder, but there's a question, and that's if it's possible with C to get just the name of a folder.
For example, let's suppose that I execute a program on this folder:
/home/sealcuadrado/work1/include
If I don't give any arguments to my program, I will use cwd() and surely I will get the absolute path to that folder.
But what I want is just the name of the actual folder, in this case include. Can this be done in C (i had seen in in Python and C#)?
Apply the basename()
function to the result of getcwd().
An alternative is to mess around getting the inode number of the current directory (.) and then open and scan the parent directory (..) looking for the name with the corresponding inode number. That gets tricky if the parent directory contains NFS auto-mount points (home directories, for example); you can end up auto-mounting an awful lot of file systems if you aren't careful (which is slow and mostly pointless).
you can parse the result of getcwd()
Maybe not the most elegant way, but this should work by using a combination of strchr() and memmove().
#include <stdio.h>
#include <string.h>
int main() {
char *s;
char buf[] = "/home/folder/include";
s = strrchr (buf, '/');
if (s != NULL) {
memmove(s, s+1, strlen(s+1)+1);
printf ("%s\n", s);
}
return 0;
}
prints include
EDIT: The following code is better and also calls getcwd()
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
char buffer[200];
char *s;
getcwd(buffer,sizeof(buffer));
s = strrchr(buffer, '/');
if (s != NULL) {
printf ("%s\n", s+1);
}
return 0;
}

Forking with command line arguments

I am building a Linux Shell, and my current headache is passing command line arguments to forked/exec'ed programs and system functions.
Currently all input is tokenized on spaces and new lines, in a global variable char * parsed_arguments. For example, the input dir /usa/folderb would be tokenized as:
parsed_arguments[0] = dir
parsed_arguments[1] = /usa/folderb
parsed_arguments tokenizes everything perfectly; My issue now is that i wish to only take a subset of parsed_arguments, which excludes the command/ first argument/path to executable to run in the shell, and store them in a new array, called passed_arguments.
so in the previous example dir /usa/folderb
parsed_arguments[0] = dir
parsed_arguments[1] = /usa/folderb
passed_arguments[0] = /usa/folderb
passed_arguments[1] = etc....
Currently I am not having any luck with this so I'm hoping someone could help me with this. Here is some code of what I have working so far:
How I'm trying to copy arguments:
void command_Line()
{
int i = 1;
for(i;parsed_arguments[i]!=NULL;i++)
printf("%s",parsed_arguments[i]);
}
Function to read commands:
void readCommand(char newcommand[]){
printf("readCommand: %s\n", newcommand);
//parsed_arguments = (char* malloc(MAX_ARGS));
// strcpy(newcommand,inputstring);
parsed = parsed_arguments;
*parsed++ = strtok(newcommand,SEPARATORS); // tokenize input
while ((*parsed++ = strtok(NULL,SEPARATORS)))
//printf("test1\n"); // last entry will be NULL
//passed_arguments=parsed_arguments[1];
if(parsed[0]){
char *initial_command =parsed[0];
parsed= parsed_arguments;
while (*parsed) fprintf(stdout,"%s\n ",*parsed++);
// free (parsed);
// free(parsed_arguments);
}//end of if
command_Line();
}//end of ReadCommand
Forking function:
else if(strstr(parsed_arguments[0],"./")!=NULL)
{
int pid;
switch(pid=fork()){
case -1:
printf("Fork error, aborting\n");
abort();
case 0:
execv(parsed_arguments[0],passed_arguments);
}
}
This is what my shell currently outputs. The first time I run it, it outputs something close to what I want, but every subsequent call breaks the program. In addition, each additional call appends the parsed arguments to the output.
This is what the original shell produces. Again it's close to what I want, but not quite. I want to omit the command (i.e. "./testline").
Your testline program is a sensible one to have in your toolbox; I have a similar program that I call al (for Argument List) that prints its arguments, one per line. It doesn't print argv[0] though (I know it is called al). You can easily arrange for your testline to skip argv[0] too. Note that Unix convention is that argv[0] is the name of the program; you should not try to change that (you'll be fighting against the entire system).
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return 0;
}
Your function command_line() is also reasonable except that it relies unnecessarily on global variables. Think of global variables as a nasty smell (H2S, for example); avoid them when you can. It should be more like:
void command_Line(char *argv[])
{
for (int i = 1; argv[i] != NULL; i++)
printf("<<%s>>\n", argv[i]);
}
If you're stuck with C89, you'll need to declare int i; outside the loop and use just for (i = 1; ...) in the loop control. Note that the printing here separates each argument on a line on its own, and encloses it in marker characters (<< and >> — change to suit your whims and prejudices). It would be fine to skip the newline in the loop (maybe use a space instead), and then add a newline after the loop (putchar('\n');). This makes a better, more nearly general purpose debug routine. (When I write a 'dump' function, I usually use void dump_argv(FILE *fp, const char *tag, char *argv[]) so that I can print to standard error or standard output, and include a tag string to identify where the dump is written.)
Unfortunately, given the fragmentary nature of your readCommand() function, it is not possible to coherently critique it. The commented out lines are enough to elicit concern, but without the actual code you're running, we can't guess what problems or mistakes you're making. As shown, it is equivalent to:
void readCommand(char newcommand[])
{
printf("readCommand: %s\n", newcommand);
parsed = parsed_arguments;
*parsed++ = strtok(newcommand, SEPARATORS);
while ((*parsed++ = strtok(NULL, SEPARATORS)) != 0)
{
if (parsed[0])
{
char *initial_command = parsed[0];
parsed = parsed_arguments;
while (*parsed)
fprintf(stdout, "%s\n ", *parsed++);
}
}
command_Line();
}
The variables parsed and parsed_arguments are both globals and the variable initial_command is set but not used (aka 'pointless'). The if (parsed[0]) test is not safe; you incremented the pointer in the previous line, so it is pointing at indeterminate memory.
Superficially, judging from the screen shots, you are not resetting the parsed_arguments[] and/or passed_arguments[] arrays correctly on the second use; it might be an index that is not being set to zero. Without knowing how the data is allocated, it is hard to know what you might be doing wrong.
I recommend closing this question, going back to your system and producing a minimal SSCCE. It should be under about 100 lines; it need not do the execv() (or fork()), but should print the commands to be executed using a variant of the command_Line() function above. If this answer prevents you deleting (closing) this question, then edit it with your SSCCE code, and notify me with a comment to this answer so I get to see you've done that.

C Test For File Existence Before Calling execvp

I'm writing a UNIX minishell on ubuntu, and am trying to add built-in commands at this point. When it's not a built-in command I fork and then the child executes it, however for built-in commands I'll just execute it in the current process.
So, I need a way to see if the files exist(if they do it's not a built-in command), however execvp uses the environment PATH variable to automatically look for them, so I have no idea how I would manually check beforehand.
So, do you guys know how I could test an argument to see if it's a built-in command simply by supplying the name?
Thanks guys.
I have tested the answer by Tom
It contained a number of problems. I have fixed them here and provided a test program.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
int is_file(const char* path) {
struct stat buf;
stat(path, &buf);
return S_ISREG(buf.st_mode);
}
/*
* returns non-zero if the file is a file in the system path, and executable
*/
int is_executable_in_path(char *name)
{
char *path = getenv("PATH");
char *item = NULL;
int found = 0;
if (!path)
return 0;
path = strdup(path);
char real_path[4096]; // or PATH_MAX or something smarter
for (item = strtok(path, ":"); (!found) && item; item = strtok(NULL, ":"))
{
sprintf(real_path, "%s/%s", item, name);
// printf("Testing %s\n", real_path);
if ( is_file(real_path) && !(
access(real_path, F_OK)
|| access(real_path, X_OK))) // check if the file exists and is executable
{
found = 1;
}
}
free(path);
return found;
}
int main()
{
if (is_executable_in_path("."))
puts(". is executable");
if (is_executable_in_path("echo"))
puts("echo is executable");
}
Notes
the test for access return value was reversed
the second strtok call had the wrong delimiter
strtok changed the path argument. My sample uses a copy
there was nothing to guarantee a proper path separator char in the concatenated real_path
there was no check whether the matched file was actually a file (directories can be 'executable' too). This leads to strange things like . being recognized as an external binary
What you can do is you can change the path to the particular directory and then use #include<dirent.h> header file and its readdir and scandir functions to walk through the directory or stat structure to see if the file exists in the directory or not.
You can iterate yourself through the PATH directories, and for each entry in PATH (You will have to split PATH with :, probably using strtok) concatenate at the end of each path the name of the command called. When you have create this path, check if the file exists and if it is executable using access.
int is_built_in(char *path, char *name)
{
char *item = strtok(path, ":");
do {
char real_path[4096] = strcat(item, name); // you would normally alloc exactly the size needed but lets stick to it for the sake of the example
if (!access(real_path, F_OK) && !access(real_path, X_OK)) // check if the file exists and is executable
return 0;
} while ((item = strtok(NULL, ":")) != NULL);
return 1;
}
Why do you want to test before calling execvp? That's the wrong approach. Just call execvp and it will tell you if the program does not exist.

Why does windows spawn process sometimes trigger error STATUS_SXS_ASSEMBLY_NOT_FOUND?

So, I have a tiny fragment of C code running on a windows box, that reads:
/* invoke command */
impl->procHandle = _spawnve(_P_NOWAIT, command, vargs, env);
if (impl->procHandle == -1) {
printf("Failed to invoke command: %s\n", strerror(errno));
impl->busy = false;
}
printf("VICTORY\n");
I wrote some unit tests around this where my "command" was C:\windows\system32\ipconfig.exe and it works, no problem.
Tried to use it for an application launcher... doo doo. Failed with the helpful error:
The application failed to initialize properly (0xc0150004).
Click on OK to terminate the application.
Ok... searching around I discovered that the error code is STATUS_SXS_ASSEMBLY_NOT_FOUND, and it happens when I try to launch notepad.exe as well. Missing assemblies?
Why is this happening?
How can I work around it?
I'm just guessing here, but I suspect it has something to do with needing the PATH variable to be set in the _spawnve(), but I dont know what it should be. I tried passing in the path, but that doesn't seem to help. Running this code:
int offset = 0;
while (vargs[offset] != NULL) {
printf("vargs %d: %s\n", offset, vargs[offset]);
++offset;
}
offset = 0;
while (env[offset] != NULL) {
printf("env %d: %s\n", offset, env[offset]);
++offset;
}
Yeilds:
vargs 0: C:\windows\system32\notepad.exe
env 0: PATH=c:\WINDOWS\system32
ie. I am passing in argv[0], and a path value; not other env variables or arguments.
Any ideas?
--
Edit:
So, it seems this error is occurring because the PATH is not correctly set when I invoke the command using _spawnve().
This is made obvious by invoking either _spawnv() or _spawnvpe(), both of which seem to work correctly.
However, that doesn't really help me, because I need to specify an additional PATH component for the application when it runs. Passing PATH=... into _spawnvpe() causes the same error, and obviously _spawnv is no used because it doesn't allow you to specify the PATH.
So really, the answer to this question is: Because the PATH variable is wrong.
...but I still have no idea what it should be. There seem to be no working examples of this that I can find anywhere. I'll accept any answer that links to an example of coding using _spawnve() or _spawnvpe() and passing the PATH variable into it (and working).
Edit #2:
Really. No, actually, this doesn't work. Here's an example of it not working. Forget linking to an example that works; just modify my example and post a diff that 1) passes in PATH and 2) runs without an error.
Nb. Want to see it work? change to _spawnv() or make the env value NULL and it runs just fine.
#include <stdio.h>
#include <windows.h>
#include <process.h>
#include <errno.h>
int main(int argc, char *argv[]) {
char *path_value;
char buffer[4000];
const char *env[2];
const char *args[1];
char *command;
int result;
intptr_t procHandle;
path_value = getenv("PATH");
sprintf(buffer, "PATH=%s", path_value);
env[0] = buffer;
env[1] = NULL;
args[0] = NULL;
int offset = 0;
while (env[offset] != NULL) {
printf("env %d: %s\n", offset, env[offset]);
++offset;
}
offset = 0;
while (args[offset] != NULL) {
printf("arg %d: %s\n", offset, args[offset]);
++offset;
}
command = "C:\\windows\\system32\\notepad.exe";
procHandle = _spawnvpe(_P_NOWAIT, command, args, NULL);
if (procHandle == -1) {
printf("Failed to invoke command: %s\n", strerror(errno));
exit(1);
}
_cwait(&result, procHandle, 0);
if (result != 0)
printf("Command exited with error code %d\n", result);
}
Output:
env 0: PATH=.;c:\Program Files\Common Files\Microsoft Shared\Windows Live;c:\WINDOWS\system32;c:\WINDOWS;c:\WINDOWS\System32\Wbem;c:\Program Files\Microsoft SQL Server\100\Tools\Binn\;c:\Program Files\Microsoft SQL Server\100\DTS\Binn\;c:\Program Files\CMake 2.8\bin;c:\Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\;c:\Program Files\Common Files\Microsoft Shared\Windows Live
Command exited with error code -1072365564
This is wrong:
const char *args[1];
args[0] = NULL;
you need
const char *args[2];
args[0] = "notepad";
args[1] = NULL;
Other than that, your example code works, at least when compiled with Visual Studio 2010. I've tested it on both Windows 7 and Windows XP, and it works. Notepad runs.
What compiler are you using?
You are right, the second parameter to _spawnev() takes the name of the app to be executed inlcuding its full path.
To get around to know the path you could call the command processer cmd.exe and pass it along the name of the app to execute as a parameter to it using cmd.exe's option /C.
This works in all the cases where you could have started the application out of one of cmd.exe's command line windows.
cmd.exe knows the value of the environment variable PATH and used it to search through it for the app's path to start.
The path to cmd.exe itself could be read from the environment variable COMSPEC.
Update: For more on this issue (including examples) please read here.
As specified here _spawn, _wspawn Functions, only the functions with a 'p' letter in the name implicitely use the PATH environment variable. The others don't.
So you need to do this:
char *args[] = {"notepad.exe", NULL };
_spawnvpe(_P_NOWAIT, args[0], args, NULL);

Win32 dispatch program and redirect stdout to file buffer problem?

On Wi32
I am trying to start a executable who redirects to a filename (current date) e.g. the same as:
Someexecutable.exe > 20101220000000.txt
When I do this from windows cmd.exe everything works fine. However when doing this from my program as shown below the system seems ot either drop the redirect even if it creates the file and/or it seems to buffer a large amount of data before flushing to disk.
I can't change the executable that is being run.
The program beeing executed now only writes to stdout, but remember I can't change this at all. (the simplest way woud be to just do stdout = filehandle; but I that is sadly impossible for me right now!)
(Not required: Also the program waits as system() this is not required but what is the simplest way of detaching the program being run via system() )
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char *argv[])
{
char execstr[512];
char s[30];
size_t i;
struct tm tim;
time_t now;
now = time(NULL);
tim = *(localtime(&now));
i = strftime(s,30,"%Y%m%d%H%M",&tim);
sprintf(execstr,"someexecutable.exe > %s.txt",s);
printf("Executing: \"%s\"\n",execstr);
system(execstr);
exit(0);
return 0;
}
I don't see any reason for this to not work, but if this is the case with you, one of the alternative solution could be to use popen and then read from the pipe for writing in the desired file. Here is some sample code which is printing on the screen. You can write that to file instead of screen/console as per your requirement.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char *argv[])
{
char execstr[512];
char s[30];
size_t i;
struct tm tim;
time_t now;
char buf[128];
FILE *pipe;
now = time(NULL);
tim = *(localtime(&now));
i = strftime(s,30,"%Y%m%d%H%M",&tim);
#if 0
sprintf(execstr,"a.exe > %s.txt",s);
printf("Executing: \"%s\"\n",execstr);
#endif /* #if 0 */
if( (pipe = _popen("a.exe", "rt")) == NULL )
exit( 1 );
while(!feof(pipe))
{
if (fgets(buf, 128, pipe) != NULL )
printf(buf); /* write to the required file here */
}
_pclose(pipe);
return 0;
}
Your program works fine for me (testing in VS 2010). Some problems you might run into if you're running your tests in the IDE are:
the current directory for the program might not be what you expect it to be (so you might be looking for the output file in the wrong place). By default, the current directory for the program when run in the IDE will be the directory that has the project file (whatever.vcproj or whatever,.vcxproj) - not the directory that has the executable. This can be changed in the project settings.
the IDE's path might not be the same as what you get at a standard command line, so you program might not be finding someexecutable.exe
If you change you program so that the line with the sprintf() call looks like:
sprintf(execstr,"someexecutable.exe",s);
Do you see the output of someexecutable.exe in the console window?

Resources