execl() arguments in Ubuntu
Asked Answered
O

6

8

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!

Omor answered 1/10, 2012 at 16:17 Comment(0)
Z
18

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.)

Zinfandel answered 1/10, 2012 at 16:25 Comment(5)
Why exactly do you think that using NULL instead of (char*)0 creates undefined behaviour?Aguilera
@JanSpurny Because the Posix standard says so. More generally, the C standard says that it is undefined behavior if a varargs function tries to extract a type other than the type which was passed. execl is documented to require a char*. It could hardly try for another type until it had extracted the argument. NULL has an integral type, usually int. So passing NULL is undefined behavior, and has been ever since pointers stopped being ints.Zinfandel
@JamesKanze: Utter bullshit. POSIX states that NULL, as a null pointer, is zero converted to a pointer [POSIX definitions, 3.244], usually (void *)0. It is either (void *)0, or an equivalent expression. In C89 and C99, void * does not need to be cast to any other pointer type. Therefore -- and this encompasses ALL C89/C99/GNU C implementations I've ever used, from microcontrollers to computational clusters -- NULL is the correct one to use for this, and all other similar variadic functions.Tret
@NominalAnimal NULL isn't a null pointer, it's a null pointer constant. And a null pointer constant is required by the C++ standard to have an integral type. I've only seen two definitions of NULL in Unix: 0 and g++'s __null compiler magic. It is always an error to pass NULL to a variadic function where pointers are expected. The Posix standard specifies using (char*)0, and the Linux man pages explicitly state that NULL must be passed as (char*)NULL.Zinfandel
@NominalAnimal Just to be perfectly clear, when NULL (or 0) is used in a context which requires a pointer type, it is implicitly converted to a null pointer. Passing an argument to a ... does not require a pointer type, however, and no conversion occurs; you pass 0. G++ uses compiler magic for NULL, so you'll probably get away with it with g++, but I've used more than a few compilers where it wouldn't work; where char* was larger than an int (and NULL was defined as 0).Zinfandel
D
4

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);
Duumvirate answered 1/10, 2012 at 16:19 Comment(2)
Both the number and the type of the arguments have to agree with what the called function expects. How the called function knows what to expect is documented. In the case of execl, the called function expects a list of char*, terminated by a null pointer of type char*. Since NULL has type int (or at least an integral type), passing it here is undefined behavior.Zinfandel
@JamesKanze again you and your undefined behaviors ...Diclinous
R
1

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.

Rivalee answered 19/3, 2014 at 12:5 Comment(0)
L
0

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

Luis answered 10/9, 2018 at 21:39 Comment(0)
A
0

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.

Agnesagnese answered 27/8, 2021 at 5:14 Comment(0)
P
0

The "NULL" represents the termination of the the commands.

Pablo answered 4/11, 2022 at 7:28 Comment(1)
This has already been mentioned in other answers.Monarchism

© 2022 - 2024 — McMap. All rights reserved.