I wrote a program which should calculate the total size of arguments passed to execve
system call.
I have tested this program with maximum size of arguments, expecting that the "Argument list too long" error will be happen only when a ARG_MAX
limit has exceeded. In my opinion, the maximum total size of command line should be as close as possible to ARG_MAX
limit, that is no additional argument (filename) can be added without exceeding of this limit.
But I see another behavior: the number of "unused" bytes fluctuates in unpredictable manner while environment and program name stays unchanged, only the number of arguments are changing.
The questions:
- the counting program is incorrect and missing some values? Why "Argument list too long" happens earlier than it should?
- this is a normal behavior and unused bytes are type of memory padding/alignment/whatever? Where this behavior is mentioned in a kernel source then? I have read linux/fs/exec.c and haven't saw something answering to my question.
Program
The counting algorithm is next:
size of argv
+ size of envp
+ size of argc
The
argv
is array of pointers to strings (pointer tochar
), so loop through this array and add to a result the lengths of strings, keeping in mind that every is ended by NULL byte. Then add their pointers to the result - the size of pointer is 8 byte. Thus:the number of pointers * 8
+lengths of strings (each with a NULL byte)
Almost the same story with
envp
- string lengths with NULL byte and pointers. But the last pointer is signalizing to the end of array by pointing to the NULL byte, so add it to the result8 bytes + 1 bytes
.The
argc
is simpleint
.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[], char *envp[]) {
size_t char_ptr_size = sizeof(char *);
// The arguments array total size calculation
size_t arg_strings_size = 0;
size_t string_len = 0;
for(int i = 0; i < argc; i++) {
// Every string ends up with a nullbyte, so the 1 byte is added
string_len = strlen(argv[i]) + 1;
arg_strings_size += string_len;
// printf("%zu:\t%s\n", string_len, argv[i]);
}
size_t argv_size = arg_strings_size + argc * char_ptr_size;
printf( "arg strings size: %zu\n"
"number of pointers to strings %i\n\n"
"argv size:\t%zu + %i * %zu = %zu\n",
arg_strings_size,
argc,
arg_strings_size,
argc,
char_ptr_size,
argv_size
);
// The enviroment variables array total size calculation
size_t env_size = 0;
for (char **env = envp; *env != 0; env++) {
char *thisEnv = *env;
// Every string ends up with a nullbyte, so the 1 byte is added
env_size += strlen(thisEnv) + 1 + char_ptr_size;
}
// The last element of "envp" is a pointer to the NULL byte, so size of pointer and 1 is added
printf("envp size:\t%zu\n", env_size + char_ptr_size + 1);
size_t overall = argv_size + env_size + sizeof(argc);
printf( "\noverall (argv_size + env_size + sizeof(argc)):\t"
"%zu + %zu + %zu = %zu\n",
argv_size,
env_size,
sizeof(argc),
overall);
// Find ARG_MAX by system call
long arg_max = sysconf(_SC_ARG_MAX);
printf("ARG_MAX: %li\n\n", arg_max);
printf("Number of \"unused bytes\": ARG_MAX - overall = %li\n\n", arg_max - (long) overall);
return 0;
}
Testing
1 byte filenames - 975 bytes unused.
$ ./program $(yes A | head -n 209222) # 209223 will cause "Argument list too long"
arg strings size: 418454
number of pointers to strings 209223
argv size: 418454 + 209223 * 8 = 2092238
envp size: 3944
overall (argv_size + env_size + sizeof(argc)): 2092238 + 3935 + 4 = 2096177
ARG_MAX: 2097152
Number of "unused bytes": ARG_MAX - overall = 975
2 bytes filenames - 3206 bytes unused.
$ ./program $(yes AA | head -n 189999)
arg strings size: 570007
number of pointers to strings 190000
argv size: 570007 + 190000 * 8 = 2090007
envp size: 3944
overall (argv_size + env_size + sizeof(argc)): 2090007 + 3935 + 4 = 2093946
ARG_MAX: 2097152
Number of "unused bytes": ARG_MAX - overall = 3206
3 bytes filenames - 2279 bytes unused.
$ ./program $(yes AAA | head -n 174243)
arg strings size: 696982
number of pointers to strings 174244
argv size: 696982 + 174244 * 8 = 2090934
envp size: 3944
overall (argv_size + env_size + sizeof(argc)): 2090934 + 3935 + 4 = 2094873
ARG_MAX: 2097152
Number of "unused bytes": ARG_MAX - overall = 2279
This question is part of my another question: How calculate the number of files which can be passed as arguments to some command for batch processing?