Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
375 views
in Technique[技术] by (71.8m points)

c - To check the E2BIG error condition in exec

I am writing a small program to test the E2BIG error condition in exec sys call. If the arguments passed exceeds the MAX_ARG as specified in the limits.h, the E2BIG error is obtained. In some cases with RLIMIT_STACK also will give error if exceeded the max argument size.

 pid = fork();
 if(0 == pid){ /*CHILD*/
  printf("
 Child : %d 
",getpid());

 getrlimit(RLIMIT_STACK,&limit);
 printf("
 cur limit : %d , max limit : %d 
",(unsigned int)limit.rlim_cur,(unsigned int)limit.rlim_max);

 limit.rlim_cur = 0;
 limit.rlim_max = 0;
 setrlimit(RLIMIT_STACK,&limit);

 printf("
 cur limit : %d , max limit : %d 
",(unsigned int)limit.rlim_cur,(unsigned int)limit.rlim_max);

  execl("/usr/bin/ls","ls",NULL);
  printf("
 Child is done! 
");
 }
 else if(pid > 0){ /*PARENT*/
  waitpid(pid,&status,0);
  printf("
 Parent : %d 
",getpid());
  printf("
 Child : %d exited with exit_status %d 
",pid,status);
 }
 else{ /*ERROR*/
  switch(errno){
        case E2BIG:
                perror("
 E2BIG");
                break;
  }
 }
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

This code probes for the limit a bit crudely, doubling the size of the argument list each time it tests the argument list size. You can refine it if you wish (so it searches in the range between the last success and first failure), but the chances are that it hits the limit correctly first time.

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>

enum { BYTES_PER_KIBIBYTE = 1024 };
enum { BYTES_PER_MEBIBYTE = BYTES_PER_KIBIBYTE * BYTES_PER_KIBIBYTE };
enum { E_GOT_E2BIG =  37 };
enum { E_NOT_E2BIG = 219 };

static void sigchld_handler(int signum)
{
    signal(signum, sigchld_handler);
}

int main(void)
{
    signal(SIGCHLD, sigchld_handler);
    for (int i = 4 * BYTES_PER_KIBIBYTE; i < BYTES_PER_MEBIBYTE; i *= 2)
    {
        fflush(0);
        pid_t pid = fork();
        if (pid < 0)
        {
            fprintf(stderr, "Failed to fork at %d
", i);
            return 1;
        }
        else if (pid == 0)
        {
            int self = getpid();
            printf("Child: %d
", self);
            char *args[10] = { "ls" };
            size_t bytes_per_arg = i / 8;
            for (int j = 1; j < 9; j++)
            {
                args[j] = malloc(bytes_per_arg + 1);
                if (args[j] == 0)
                {
                    fprintf(stderr, "Failed to allocate argument space at size %d
", i);
                    exit(E_NOT_E2BIG);
                }
                memset(args[j], j + '0', bytes_per_arg);
                args[j][bytes_per_arg] = '';
            }

            /* Close standard I/O channels so executed command doesn't spew forth */
            int dev_null = open("/dev/null", O_RDWR);
            if (dev_null < 0)
            {
                fprintf(stderr, "Failed to open /dev/null for reading and writing
");
                exit(E_NOT_E2BIG);
            }
            int dev_stderr = dup(2);
            if (dev_stderr < 0)
            {
                fprintf(stderr, "Failed to dup() standard error
");
                exit(E_NOT_E2BIG);
            }
            close(0);
            dup(dev_null);
            close(1);
            dup(dev_null);
            close(2);
            dup(dev_null);
            close(dev_null);

            /* Execute ls on big file names -- error is ENAMETOOLONG */
            execvp(args[0], args);

            /* Reinstate standard error so we can report failure */
            dup2(dev_stderr, 2);
            int errnum = errno;
            if (errnum == E2BIG)
            {
                fprintf(stderr, "%d: got E2BIG (%d: %s) at size %d
", self, errnum, strerror(errnum), i);
                exit(E_GOT_E2BIG);
            }
            fprintf(stderr, "%d: got errno %d (%s) at size %d
", self, errnum, strerror(errnum), i);
            exit(E_NOT_E2BIG);
        }
        else
        {
            int self = getpid();
            int corpse;
            int status;
            int exit_loop = 0;
            while ((corpse = waitpid(pid, &status, 0)) != -1)
            {
                if (!WIFEXITED(status))
                    printf("%d: child %d died with exit status 0x%.4X", self, corpse, status);
                else
                {
                    int statval = WEXITSTATUS(status);
                    printf("%d: child %d died with exit status %d: ", self, corpse, statval);
                    switch (statval)
                    {
                    case E_GOT_E2BIG:
                        printf("success: got E2BIG");
                        exit_loop = 1;
                        break;
                    case E_NOT_E2BIG:
                        printf("failed: indeterminate error in child");
                        break;
                    case 1:
                        printf("command exited with status 1 - it worked");
                        break;
                    default:
                        printf("unknown: unexpected exit status %d", statval);
                        break;
                    }
                }
                printf(" at size %d (%d KiB)
", i, i / BYTES_PER_KIBIBYTE);
                fflush(stdout);
            }
            if (exit_loop)
                break;
        }
    }
    return 0;
}

Sample run:

46573: child 46575 died with exit status 1: command exited with status 1 - it worked at size 4096 (4 KiB)
46573: child 46576 died with exit status 1: command exited with status 1 - it worked at size 8192 (8 KiB)
46573: child 46577 died with exit status 1: command exited with status 1 - it worked at size 16384 (16 KiB)
46573: child 46578 died with exit status 1: command exited with status 1 - it worked at size 32768 (32 KiB)
46573: child 46579 died with exit status 1: command exited with status 1 - it worked at size 65536 (64 KiB)
46573: child 46580 died with exit status 1: command exited with status 1 - it worked at size 131072 (128 KiB)
46581: got E2BIG (7: Argument list too long) at size 262144
46573: child 46581 died with exit status 37: success: got E2BIG at size 262144 (256 KiB)

SIGCHLD handling

The SIGCHLD handling code is irrelevant. This is a more refined version of the code above. It does a binary search for the size of the argument list. On my machine (Mac OS X 10.8.4), allowing for the environment (which counts as part of the argument size), the limit is 256 KiB.

/* SO 18559403: How big an argument list is allowed */

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

extern char **environ;  /* Sometimes in <unistd.h> */

enum { BYTES_PER_KIBIBYTE = 1024 };
enum { BYTES_PER_MEBIBYTE = BYTES_PER_KIBIBYTE * BYTES_PER_KIBIBYTE };
enum { E_GOT_E2BIG =  37 };
enum { E_NOT_E2BIG = 219 };

enum { R_TOO_LARGE = +1, R_TOO_SMALL = -1 };

static char *print_kib(int size, char *buffer, size_t buflen)
{
    snprintf(buffer, buflen, "%d (%d KiB)", size, size / BYTES_PER_KIBIBYTE);
    return buffer;
}

static int test_arg_size(int size)
{
    char buffer[32];
    int result = R_TOO_SMALL;
    assert(size % 8 == 0);
    fflush(0);
    pid_t pid = fork();
    if (pid < 0)
    {
        fprintf(stderr, "Failed to fork at size %s
",
                print_kib(size, buffer, sizeof(buffer)));
        exit(1);
    }
    else if (pid == 0)
    {
        int self = getpid();
        printf("Child: %d
", self);
        char *args[10] = { "ls" };
        size_t bytes_per_arg = size / 8;
        for (int j = 1; j < 9; j++)
        {
            args[j] = malloc(bytes_per_arg);
            if (args[j] == 0)
            {
                fprintf(stderr, "Failed to allocate argument space at size %s
",
                        print_kib(size, buffer, sizeof(buffer)));
                exit(E_NOT_E2BIG);
            }
            memset(args[j], j + '0', bytes_per_arg - 1);
            args[j][bytes_per_arg - 1] = '';
        }

        /* Close standard I/O channels so executed command doesn't spew forth */
        int dev_null = open("/dev/null", O_RDWR);
        if (dev_null < 0)
        {
            fprintf(stderr, "Failed to open /dev/null for reading and writing
");
            exit(E_NOT_E2BIG);
        }
        int dev_stderr = dup(2);
        if (dev_stderr < 0)
        {
            fprintf(stderr, "Failed to dup() standard error
");
            exit(E_NOT_E2BIG);
        }
        close(0);
        /*
        ** GCC on Linux generates warnings if you don't pay attention to
        ** the value returned by dup().
        */
        int fd = dup(dev_null);
        assert(fd == 0);
        close(1);
        fd = dup(dev_null);
        assert(fd == 1);
        close(2);
        fd = dup(dev_null);
        assert(fd == 2);
        close(dev_null);

        /* Execute ls on big file names -- error is ENAMETOOLONG */
        execvp(args[0], args);

        /* Reinstate standard error so we can report failure */
        dup2(dev_stderr, 2);
        int errnum = errno;
        if (errnum == E2BIG)
        {
            fprintf(stderr, "%d: got E2BIG (%d: %s) at size %s
",
                    self, errnum, strerror(errnum),
                    print_kib(size, buffer, sizeof(buffer)));
            exit(E_GOT_E2BIG);
        }
        fprintf(stderr, "%d: got errno %d (%s) at size %s
",
                self, errnum, strerror(errnum),
                print_kib(size, buffer, sizeof(buffer)));
        exit(E_NOT_E2BIG);
    }
    else
    {
        int self = getpid();
        int corpse;
        int status;
        while ((corpse = waitpid(pid, &status, 0)) != -1)
        {
            if (!WIFEXITED(status))
                printf("%d: child %d died with exit status 0x%.4X",
                       self, corpse, status);
            else
            {
                int statval = WEXITSTATUS(status);
                printf("%d: child %d died with exit status %d: ",
                       self, corpse, statval);
                switch (statval)
                {
                case E_GOT_E2BIG:
                    printf("success: got E2BIG");
                    result = R_TOO_LARGE;
                    break;
                case E_NOT_E2BIG:
                    printf("failed: indeterminate error in child");
                    break;
                    /*
                    ** ls on Mac OS X fails with 1 if it fails to find a
                    ** file.  On Linux, it exits with 1 for 'minor
                    ** problems' (e.g. cannot access subdirectory).
                    ** ls on Linux fails with 2 if it fails with 'serious
                    ** trouble'; (e.g. if it can't find a file)
                    */
                case 1:
                case 2:
                    printf("command exited with status %d - it worked", statval);
                    break;
                default:
                    printf("unknown: unexpected exit status %d", statval);
                    break;
                }
            }
            printf(" at size %s
", print_kib(size, buffer, sizeof(buffer)));
            fflush(stdout);
        }
    }
    return result;
}

static int env_size(void)
{
    int size = 0;
    for (char **ep = environ; *ep != 0; ep++)
        size += strlen(*ep) + 1;
    return size;
}

int main(void)
{
    int env = env_size();
    int lo = 0;
    int hi = 4 * BYTES_PER_MEBIBYTE;

    /* Binary search */
    /* The kilobyte slop means termination does not have to be accurate */
    while (lo + 1 * BYTES_PER_KIBIBYTE < hi)
    {
        int mid = (lo + hi) / 2;
        if (test_arg_size(mid) == R_TOO_LARGE)
            hi = mid;
        else
            lo = mid;
    }

    char buffer1[32];
    char buffer2[32];
    printf("Environment size = %d
", env);
    printf("Best guess: maximum argument size in range %s to %s
",
           print_kib(lo + env, buffer1, sizeof(buffer1)),
           print_kib(hi + env, buffer2, sizeof(buffer2)));

    return 0;
}

Updated 2014-04-06: compiles and runs better on Linux, where ls distinguishes between minor problems and serious trouble with exit statuses 1 and 2, and where GCC is told to complain if you don't capture the result of dup(). Also tests up to 4 MiB argument space, up from previous 1 MiB. (Change the initial value of hi in main()


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...