I saw the following usage of execl:
execl("/bin/ls", "ls", "-l", "-R", "-a", NULL);
I'm wondering why the first parameter is ls, it's already in the first parameter why to add it again?
can it be NULL? what's the impact?
You can check the execl's man document like below.
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
The const char *arg and subsequent ellipses in the execl(), execlp(), and execle() functions can be thought of as arg0, arg1, ..., argn. Together they describe a list of one or more pointers to null-terminated strings that represent the argument list available to the executed program. **The first argument, by convention, should point to the filename associated with the file being executed.** The list of arguments must be terminated by a null pointer, and, since these are variadic functions, this pointer must be cast (char *) NULL.
The first parameter points to the filename associated with the file being executed.
BR,
Related
I came across a function definition like:
char* abc(char *f, ...)
{
}
What do the three dots mean?
These type of functions are called variadic functions (Wikipedia link). They use ellipses (i.e., three dots) to indicate that there is a variable number of arguments that the function can process. One place you've probably used such functions (perhaps without realising) is with the various printf functions, for example (from the ISO standard):
int printf(const char * restrict format, ...);
The ellipses allow you to create functions where the number of parameters are not known beforehand, and you can use stdargs.h functions (va_start, va_arg and va_end) to get the specific arguments.
You do have to know the types of the arguments you extract and have some way of deciding when you're done. The printf functions do this with the format string (for both types and count), while my example code below always assumes const char * as the type with a sentinel value NULL to decide completion.
This link here has a good treatise on the use of variable argument lists in printf.
As an example, the following program contains a function outStrings(), that allows you to print an arbitrary number of strings:
#include <stdio.h>
#include <stdarg.h>
void outStrings(const char *strFirst, ...) {
// First argument handled specially.
printf("%s", strFirst);
va_list pArg;
va_start(pArg, strFirst);
// Just get and process each string until NULL given.
const char *strNext = va_arg(pArg, const char *);
while (strNext != NULL) {
printf("%s", strNext);
strNext = va_arg(pArg, const char *);
}
// Finalise processing.
va_end(pArg);
}
int main(void) {
char *name = "paxdiablo";
outStrings("Hello, ", name, ", I hope you're feeling well today.\n", NULL);
}
Wikipedia on vararg functions in C++.
They are called an elipsis and they mean that the function can take an indeterminate number of parameters. Your function can probably be called like this:
abc( "foo", 0 );
abc( "foo", "bar", 0 );
There needs to be a way of indicating the end of the list. This can be done by using the first parameter, as ion a printf(0 format string, or by a special terminator, zero in the example above.
Functions with a variable number of parameters are considered bad form in C++, as no type checking or user defined conversions can be performed on the parameters.
This is what is called a varargs function or a variable argument function in C.
One you'll probably recognise is printf.
Basically I am working on integrating an existing C++ file with our javascript / node using their NAPI, functionality. I have it working on a test C++ file so I know I have the setup working. However on the actual C++ file it is designed to run from the command line with argc and argv from the command line. Basically I just need to invoke the main method in C++ from inside of my other function, which means there is no command line. So I have to pass in values for argc and argv. argc is just an int, that is easy enough, but argc is a char ** type, which from my research looks like it is an array of character arrays aka strings?
This is my current code at the bottom of my c++ file
void Init(Env env, Object exports, Object module) {
exports.Set("main", Function::New(env, main(2,{"test","test2"})));
}
NODE_API_MODULE(addon, Init)
The argc value is working fine
I am trying to create a temporary / test value for argv to pass in but I am having an issue figuring out how to make an array of type char ** with my values.
argv is an array of pointers to strings (actually, NUL-terminated character arrays), where element 0 is the name of the program, elements 1 ... argc-1 are the program arguments, and element argc must be NULL1.
There's no array literal in C++, so you have to create explicitly an array variable to pass it to a function. Even worse, main is allowed to modify the passed arguments, so you cannot even build an array of string literals, as they are read only; thus, you have to explicitly allocate read/write space for each argument. A barebones solution can be to have single buffers for each argument and build the pointer array out of them:
char argv0[] = "test_program";
char argv1[] = "arg1";
char argv2[] = "arg2";
char *argv[] = {argv0, argv1, argv2, NULL};
main(3, argv);
A more flexible one (especially if you have to build your arguments dynamically) can be to use an std::vector<std::string>:
std::vector<std::string> args = { "test_program", "arg1", "arg2" };
// ... here you may add other arguments dynamically...
args.push_back("arg3"); // whatever
// build the pointers array
std::vector<char *> argv;
for(std::string &s: args) argv.push_back(&s[0]);
argv.push_back(NULL);
main(argv.size()-1, argv.data());
Now, coming to your code:
void Init(Env env, Object exports, Object module) {
exports.Set("main", Function::New(env, main(2,{"test","test2"})));
}
NODE_API_MODULE(addon, Init)
besides the fact that you cannot build argv to pass to main like that, you are trying to invoke main and pass its result as second argument to Function::New, while Function::New wants some callable type (e.g. a function pointer) to register as handler for the export named main! Copying from the Function::New documentation:
/// Callable must implement operator() accepting a const CallbackInfo&
/// and return either void or Value.
So, as a simple example, you could export your main as a parameterless JS function that returns nothing (undefined, I guess?) by registering a callback like this:
void MainCallback(const CallbackInfo& info) {
char argv0[] = "test_program";
char argv1[] = "arg1";
char argv2[] = "arg2";
char *argv[] = {argv0, argv1, argv2, NULL};
main(3, argv);
}
void Init(Env env, Object exports, Object module) {
exports.Set("main", Function::New(env, MainCallback));
}
NODE_API_MODULE(addon, Init)
Finally, as others said, technically in C++ main is somewhat magic - it's undefined behavior to invoke it from inside the program; in practice, on any platform that I know of that can also run node.js, main is a perfectly regular function that happens to be invoked by the C runtime at startup, so I don't think that this will cause you any problem.
Notes
So, you could say it's a NULL-terminated array of NUL-terminated character arrays. Notice that here NULL = null pointer; NUL = string terminator, i.e. '\0'.
Under the context of system programming in UNIX environment, while using the programming language C++, in my understanding, execl() will pass in the path of the program it will run, and a vector. When that vector is being passed in, I understand it as being passed into the entry point, which is usually the main function. In a main function, I understand that my parameters can be written as:
int main(int argc, int* argv[]){ return 0; }
With the above context in mind, when arguments are being passed into the execl(), it seems to me that it doesn't get directly passed into the main function.
Is there a "processing" stage where arguments of execl() are changed to integer data type and an array?
At the same time, if there's any faults in my understanding, please, feel free to correct them.
The signature you give for the main function is incorrect. It should be:
int main(int argc, char *argv[]);
Or:
int main(void);
As for the arguments, the arguments passed to execl should match what the called program receives.
For example, if program A execs program B like this:
execl("/path/to/progB", "progB", "-a", "1", "-x", "hello", "command", (char *)NULL);
Then in program B, argc will be 6 and argv will essentially be:
{ "progB", "-a", "1", "-x", "hello", "command" }
Your understanding is wrong. From this documentation the argument list is always a number of null-terminated char* pointers (emphasis mine):
execl(<shell path>, arg0, file, arg1, ..., (char *)0);
where is an unspecified pathname for the sh utility, file is the >> process image file, and for execvp(), where arg0, arg1, and so on correspond to the values passed to execvp() in argv[0], argv[1], and so on.
The arguments represented by arg0,... are pointers to null-terminated character strings. These strings shall constitute the argument list available to the new process image. The list is terminated by a null pointer. The argument arg0 should point to a filename string that is associated with the process being started by one of the exec functions.
So that doesn't match what you're claiming:
int main(int argc, int* argv[]){ return 0; }
// ^^^^
main takes a char*argv[] argument (not int *argv[]) just like the usual core exec sysccall usually does. On Linux, the system call is execve (requires a char*[])and all other exec* functions are implemented in terms of that.
As for execl, the argument list is required to be NULL-terminated, which allows you to count the arguments and then copy them into an array that gets passed down to execve.
The musl libc library does that rather straightforwardly: https://git.musl-libc.org/cgit/musl/tree/src/process/execl.c
I am learning linux programming and came across exec function which is kind of very useful. But the problem is exec function arguments are very confusing and I am unable to grasp which argument is for what purpose.. In the following code execl() function is called from a child created through fork(), What is the purpose of the last argument (NULL) in execl()?
execl("/bin/ls","ls","-l",NULL);
If any one can explain what is the purpose of NULL argument and other arguments and the purpose of arguments of exec() family function, It would be a great help to me!
To create undefined behavior. That is not a legal call to execl. A
correct call might be:
execl( "/bin/ls", "ls", "-l", (char*)0 );
The last argument must be (char*)0, or you have undefined behavior.
The first argument is the path of the executable. The following
arguments appear in argv of the executed program. The list of these
arguments is terminated by a (char*)0; that's how the called function
knows that the last argument has been reached. In the above example,
for example, the executable at "/bin/ls" will replace your code; in
its main, it will have argc equal 2, with argv[0] equal "ls",
and argv[1] equal "-l".
Immediately after this function, you should have the error handling
code. (execl always returns -1, when it returns, so you don't need to
test it. And it only returns if there was some sort of error.)
The exec functions are variadic: they take a variable number of parameters so that you can pass a variable number of arguments to the command. The functions need to use NULL as a marker to mark the end of the argument list.
Within variadic functions is a loop that will iterate over the variable number of arguments. These loops need a terminating condition. In some cases, e.g. printf, the actual number of arguments can be inferred from another argument. In other functions, NULL is used to mark the end of the list.
Another option would be to add an additional function parameter for number of arguments, but that would make the code a little more brittle, requiring the programmer to manage an additional parameter, rather than simply always using a NULL as the final argument.
You'll also see (char *) 0 used as the marker:
execl("/bin/ls", "ls", "-l", (char *) 0);
In /usr/include/libio.h, since gcc 2.8 (a long time ago) NULL is defined to be null ( is reserved for builtins), prior to that NULL was (void *)0 which is indistinguishable from (char *)0 in a varargs situation since the type is not passed, the exception being if __cplusplus is defined in which case NULL is defined as 0.
The safe thing to do especially if you have a 64-bit architecture is to explicitly use (void *)0 which is defined to be compatible with any pointer and not rely on any dodgy #defines that might happen to be in the standard library.
The purpose of the ending argument (char *) 0 is to terminate the parameters. Undefined behavior may result if this is missing.
The man page defines the execl signature as :
int execl(const char *path, const char *arg, ...);
path: The location of the program to invoke, the program to invoke.
arg, ...*: can be thought of as arg0, arg1, ..., argn.
In your case execl( "/bin/ls", "ls", "-l", (char*)0 ); is the correct function call.
"bin/ls" the program to invoke
"ls" the program name
"-l" is the parameter for that program called
The glibc-2.34 implementation of the POSIX execl function looks like this below.
/* Copyright (C) 1991-2021 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <unistd.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/param.h>
#include <stddef.h>
/* Execute PATH with all arguments after PATH until
a NULL pointer and environment from `environ'. */
int
execl (const char *path, const char *arg, ...)
{
ptrdiff_t argc;
va_list ap;
va_start (ap, arg);
for (argc = 1; va_arg (ap, const char *); argc++)
{
if (argc == INT_MAX)
{
va_end (ap);
errno = E2BIG;
return -1;
}
}
va_end (ap);
/* Avoid dynamic memory allocation due two main issues:
1. The function should be async-signal-safe and a running on a signal
handler with a fail outcome might lead to malloc bad state.
2. It might be used in a vfork/clone(VFORK) scenario where using
malloc also might lead to internal bad state. */
ptrdiff_t i;
char *argv[argc + 1];
va_start (ap, arg);
argv[0] = (char *) arg;
for (i = 1; i <= argc; i++)
argv[i] = va_arg (ap, char *);
va_end (ap);
return __execve (path, argv, __environ);
}
libc_hidden_def (execl)
The first loop computes argc, the number of arguments to execl (including the first argument, path). The condition of the first loop is va_arg (ap, const char *). If you use 0 as the last argument, the macro will expand to something like:
const char* temp = 0;
...
return temp;
Any scalar zero value assigned this way is false. So using 0 as the last argument will terminate the loop.
The second loop forms the argc-length array of arguments, argv. Note each argument is explicitly casted to char* on assignment. In the case of 0 as the last argument, the resulting value in argv from the macro expansion will be as though you'd written:
char* p = 0;
argv[i] = p;
...
Again, this is definitely a zero value, and will evaluate to false in the loop condition within execve. Unpopular opinion, but using 0 as the last argument in execl is technically correct. You don't have to stress about the int (32-bit) -> char* (64-bit) "it's implementation and might not pad with zeroes" conversion b.s., because va_arg is a macro - it defines a char* on the spot and assigns 0 to it, which guarantees it will have a false logical value.
The "NULL" represents the termination of the the commands.
I came across a function definition like:
char* abc(char *f, ...)
{
}
What do the three dots mean?
These type of functions are called variadic functions (Wikipedia link). They use ellipses (i.e., three dots) to indicate that there is a variable number of arguments that the function can process. One place you've probably used such functions (perhaps without realising) is with the various printf functions, for example (from the ISO standard):
int printf(const char * restrict format, ...);
The ellipses allow you to create functions where the number of parameters are not known beforehand, and you can use stdargs.h functions (va_start, va_arg and va_end) to get the specific arguments.
You do have to know the types of the arguments you extract and have some way of deciding when you're done. The printf functions do this with the format string (for both types and count), while my example code below always assumes const char * as the type with a sentinel value NULL to decide completion.
This link here has a good treatise on the use of variable argument lists in printf.
As an example, the following program contains a function outStrings(), that allows you to print an arbitrary number of strings:
#include <stdio.h>
#include <stdarg.h>
void outStrings(const char *strFirst, ...) {
// First argument handled specially.
printf("%s", strFirst);
va_list pArg;
va_start(pArg, strFirst);
// Just get and process each string until NULL given.
const char *strNext = va_arg(pArg, const char *);
while (strNext != NULL) {
printf("%s", strNext);
strNext = va_arg(pArg, const char *);
}
// Finalise processing.
va_end(pArg);
}
int main(void) {
char *name = "paxdiablo";
outStrings("Hello, ", name, ", I hope you're feeling well today.\n", NULL);
}
Wikipedia on vararg functions in C++.
They are called an elipsis and they mean that the function can take an indeterminate number of parameters. Your function can probably be called like this:
abc( "foo", 0 );
abc( "foo", "bar", 0 );
There needs to be a way of indicating the end of the list. This can be done by using the first parameter, as ion a printf(0 format string, or by a special terminator, zero in the example above.
Functions with a variable number of parameters are considered bad form in C++, as no type checking or user defined conversions can be performed on the parameters.
This is what is called a varargs function or a variable argument function in C.
One you'll probably recognise is printf.