Why execvp accepts 2 arguments - c

Assume the following code in c:
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char * argv[]) {
char *args[] = {"/bin/echo", "1", NULL};
execvp(args[0], args);
}
Why does execvp accept 2 arguments? Why can't it be execvp(args) and internally it would automatically grab the executable to run from args[0] ?

What if you want the "name" of the process (args[0] in your case) to be different from the actual executable program? That is a valid use-case, and the simplest solution to handle both the cases is the two-argument execvp we now have.

Related

Running find from a C program to print file name given inode number?

I am trying to print the file name given the inode number. I am using the execlp system call to run the find command. My code is shown below:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
char str[8];
char *ptr;
ptr=str;
long x=9306140;
snprintf(str,8,"%ld", x);
execlp("find"," ","~"," ","-inum"," ",str,NULL);
}
But I get an error: Invalid argument '9306140' to -inum.
Could someone please help?
As noted in comments, you don't want the " " (space) arguments in the execlp() argument list. The shell takes a string with spaces separating the arguments, but it treats what's between the spaces as words that are passed to the command; it doesn't ordinarily pass the spaces to the command. (That's a simple and sufficient explanation for this exercise; there are caveats and weasel-words galore that could be added.)
The shell also expands ~ to match the value in the $HOME environment variable (in contrast to ~user which gets the home directory of user from the password file — they're usually, but not necessarily, the same).
You also have little margin for error in the size of string you've allocated for the number. Frankly, though, you should simply use a string. All these changes lead to:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
const char *home = getenv("HOME");
const char inode[] = "9306140";
execlp("find", "find", home, "-inum", inode, (char *)NULL);
fprintf(stderr, "failed to execute 'find' command\n");
return(EXIT_FAILURE);
}
Note the repeated "find"; the first is the name of the file to be executed after searching along the path; the second is the value to be provided as argv[0]. You could put "hypochondriac" as the second occurrence and it is likely that find would work the same, at worst reporting its error messages as coming from the program 'hypochondriac'.
The next step would be to take the inode number from a command line argument:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s inode\n", argv[0]);
exit(EXIT_FAILURE);
}
const char *home = getenv("HOME");
const char *inode = argv[1];
execlp("find", "find", home, "-inum", inode, (char *)NULL);
fprintf(stderr, "%s: failed to execute 'find' command\n", argv[0]);
return(EXIT_FAILURE);
}
The step after that would be to handle multiple inode values; at that point, you use execvp() instead of execlp(), though (or, if you're desparate and lazy, you loop and fork() and execlp() once per inode number, but that's slamming your system for no good reason at all).

Running BASH command via C program

I'm trying to run BASH commands via a C program, but i'm struggling with the function execv. I don't really know how to write the first parameter of that function. I tried with the strcat function to append the string "/bin/" with the 1st element of the argv tab, which is the command i write when I run my program, but it just doesn't work. I get a "Segmentation fault". Instead of using the strcat function I tried with strdup, but I don't know how to use it right.
Any help would be appreciated. My program is below.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[], char *envp[]){
char *tab[] = {argv[1],NULL};
if(execve(strcat("/bin/",argv[1]), tab, envp)==-1)
{
perror("execve");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
regarding:
if(execve(strcat("/bin/",argv[1]), tab, envp)==-1)
This will not work ,
the literal "/bin/" is in read only memory, so cannot be changed (need a char buffer large enough to hold the full string, similar to `char string[100] = "/bin/";
Suggest:
#include <stdio.h> // perror()
#include <stdlib.h> // exit(), EXIT_FAILURE
#include <sys/types.h>
#include <sys/wait.h> // waitpid()
#include <unistd.h> // fork(), execvp()
#include <string.h> // strlen(), strcpy(), strcat()
int main( int argc, char *argv[], char *env[] )
{
(void)argc;
char *tab[] = { argv[1], NULL };
char string[strlen(argv[1]) + strlen( "/bin/" ) +1 ];
strcpy( string, "/bin/" );
strcat( string, argv[1] );
int status;
pid_t pid = fork();
switch( pid )
{
case -1: // fork failed
perror( "fork failed" );
exit( EXIT_FAILURE );
break;
case 0: // child process
execve( string, tab, env ); // does not return unless an error
perror("execve failed");
exit( EXIT_FAILURE );
break;
default:
waitpid( pid, &status, 0 );
break;
}
}
Caveat: the proposed code just hides the parameter: argc rather than properly checking it to assure the command line does contain a parameter.
Caveat: the parameter to main: env[] is not portable and should not be used. Suggest using :
extern char *environ[];
To run a shell command from a C program, you should use system(3).
If you want to get its stdout (or give its stdin, but not both) use popen(3) (don't forget to pclose such a stream).
The shell used (by system and popen) is not exactly bash but the standard POSIX /bin/sh (quite similar to bash with some restrictions).
To build that shell command (but beware of code injections in it) you can use common string functions such as snprintf and asprintf.
Notice that execve(2) does not return when it is successful, and it does not run a command thru a shell, but directly an executable program. Actually Unix shells (such as bash or  /bin/sh) are using fork(2), execve(2), waitpid(2) very often and are implementing globbing. BTW system & popen are also using fork and execve on /bin/sh -c.
strcat("/bin/",argv[1])
is horribly wrong, the first argument to strcat is the overwritten destination buffer (so cannot be a string literal), and you don't check against buffer overflow.
You might want to code:
char progbuf[80];
int ln = snprintf(progbuf, sizeof(progbuf), "/bin/%s", argv[1]);
and you should check later that ln<(int)sizeof(progbuf)
BTW, your program, when you'll improve it, is not using Bash; it is directly executing some command.
I tried with strdup, but I don't know how to use it right.
Before using any function, you need to carefully read its documentation, for example strdup(3) (or type man strdup in a terminal).
Melpomene is right- you can't use strcat like that. In C, you can't return strings. What you do is pass a memory address (pointer) as the first argument in strcat, and then strcat modifies the memory pointed to by it so that it also contains the second argument of strcat. This is a process you will repeat over and over again in C, so you should understand it. Also, that strcat doesn't look safe, I bet there is a strcatn function or something like that.
I finally got to find a way to do what I wanted at the first place. Thanks to all of you guys for your help & advices ! Here's the solution !
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[], char *envp[]){
char *tab[] = {argv[1],argv[2],NULL};
char test[20] = "/bin/";
if(execve(strcat(test,argv[1]), tab, envp)==-1)
{
perror("execve");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

C: How can I exec() my program with the same command line arguments as my running program

I am trying to reset my program when it receives a SIGSEGV by using ececl() in my signal handler. But, my current program needs commandline arguments to start that I can pass via execl() + 1 extra argument "RESTART" to notify the program that it just restarted instead of a fresh start.
But how can I pass my argv[] via exec()?
Objective:
execl("./myprog","./myprog",argv[1],argv[2],...,argv[argc],"RESTART");
OR
execl("./myprog","./myprog","RESTART",argv[1],argv[2],...,argv[argc]);
Use execv():
SYNOPSIS
#include <unistd.h>
...
int execv(const char *path, char *const argv[]);
...
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.
Perhaps like this:
int main( int argc, char **argv )
{
...
int rc = execv( "./myprog", argv );
}
You may need to modify specific values in argv or create an entirely new argument array to fit what you need.
You need to save argv in a global, either from main:
static char **Argv;
int main(int c, char **v) { Argv = v; //...
or from a gcc constructor:
static char **Argv;
__attribute__((constructor))
static void ctor(int c, char **v) { Argv = v; }
Then you can do what you want:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
static char **Argv;
static void hndlr(int s)
{
execv("/proc/self/exe", Argv);
_exit(127);
}
int main(int argc, char **argv)
{
Argv = argv;
struct sigaction sa = { .sa_handler = hndlr, .sa_flags = SA_NODEFER };
sigaction(SIGSEGV, &sa, 0);
sleep(1);
fputs("start\n", stderr);
//keep re-executing the same program
raise(SIGSEGV);
}
Note that without the SA_NODEFER, you'll only see the message twice, because SIGSEGV will be blocked during the second run of the executable.
While this should be defined (especially if you add a signal stack so that you can handle stack overflows with this too), wrappers scripts/programs are a safer and more robust way of doing this. With the SISEGV handler approach, you aren't really starting from scratch -- you are inheriting signal masks, effective uids/gids, workings directories, open file descriptors, etc. etc., whereas with a wrapper script you start from a well defined state.

having some troubles with execv (bash c)

i want to write a program which executes the command passed in argument to a .c file, but i'm having incomprehensible errors coming from the declaration of an array of char pointers; my code is the following:
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
if(argc < 2)
{
printf("Incorrect number of arguments.\n");
return -1;
}
char* args[1 + argc - 2]; // this is causing the problem
char cmd[10];
/*
* some operations...
*/
char* test[] = {"ps","-l","NULL"};
execv("/bin/ps",test);
return 0;
}
the arguments of execv should normally be cmd and args, but it didnt work, so i tried a simple command, didnt work either, i'm getting the following error:
error: unsupported option (BSD syntax)
Usage:
ps [options]
Try 'ps --help <simple|list|output|threads|misc|all>'
or 'ps --help <s|l|o|t|m|a>'
for additional help text.
For more details see ps(1).
however, if i put 1 as the size of the args array, the ps command is executed... i'm pretty confused, and i'd appreciate any kind of help

C How to pass exact number of arguments in command line

I am learning C by myself, and I am writing a program that accepts commandline arguments.
main(int argc, char **argv)
chdir (argv[1]);
I was wondering, if there is any way I can limit the number of arguments that can be passed to this program, say, how much number of arguments I pass, it accepts only first 2 only? I was thinking of this because I don't want to keep a huge number of arguments in the program's memory(may be silly thinking).
There is no way to limit how many arguments the OS can pass to your program programatically... however, you can do one of two things:
Ignore extra arguments
exit with an error.
#include <stdio.h>
#include <stdlib.h>
#define EXPECTED_NUMBER_OF_ARGUMENTS (2)
int main(int argc, char *argv[])
{
if(argc != EXPECTED_NUMBER_OF_ARGUMENTS)
{
fprintf(stderr, "usage: %s (val)", argv[0]);
exit(EXIT_FAILURE);
}
...
exit(EXIT_SUCCESS);
}

Resources