1

exec sys コールで E2BIG エラー状態をテストする小さなプログラムを作成しています。渡された引数が、limits.h で指定されている MAX_ARG を超えると、E2BIG エラーが発生します。RLIMIT_STACK を使用すると、引数の最大サイズを超えるとエラーが発生する場合があります。

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

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

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

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

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

1 に答える 1

2

このコードは、引数リストのサイズをテストするたびに引数リストのサイズを 2 倍にして、少し大雑把に制限を調べます。必要に応じて絞り込むことができます (つまり、最後の成功と最初の失敗の間の範囲で検索します) が、最初に正しく制限に達する可能性があります。

#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\n", i);
            return 1;
        }
        else if (pid == 0)
        {
            int self = getpid();
            printf("Child: %d\n", 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\n", i);
                    exit(E_NOT_E2BIG);
                }
                memset(args[j], j + '0', bytes_per_arg);
                args[j][bytes_per_arg] = '\0';
            }

            /* 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\n");
                exit(E_NOT_E2BIG);
            }
            int dev_stderr = dup(2);
            if (dev_stderr < 0)
            {
                fprintf(stderr, "Failed to dup() standard error\n");
                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\n", self, errnum, strerror(errnum), i);
                exit(E_GOT_E2BIG);
            }
            fprintf(stderr, "%d: got errno %d (%s) at size %d\n", 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)\n", i, i / BYTES_PER_KIBIBYTE);
                fflush(stdout);
            }
            if (exit_loop)
                break;
        }
    }
    return 0;
}

サンプルラン:

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 処理

SIGCHLD 処理コードは無関係です。これは、上記のコードのより洗練されたバージョンです。引数リストのサイズのバイナリ検索を行います。私のマシン (Mac OS X 10.8.4) では、環境 (引数サイズの一部としてカウントされる) を考慮して、制限は 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\n",
                print_kib(size, buffer, sizeof(buffer)));
        exit(1);
    }
    else if (pid == 0)
    {
        int self = getpid();
        printf("Child: %d\n", 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\n",
                        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] = '\0';
        }

        /* 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\n");
            exit(E_NOT_E2BIG);
        }
        int dev_stderr = dup(2);
        if (dev_stderr < 0)
        {
            fprintf(stderr, "Failed to dup() standard error\n");
            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\n",
                    self, errnum, strerror(errnum),
                    print_kib(size, buffer, sizeof(buffer)));
            exit(E_GOT_E2BIG);
        }
        fprintf(stderr, "%d: got errno %d (%s) at size %s\n",
                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\n", 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\n", env);
    printf("Best guess: maximum argument size in range %s to %s\n",
           print_kib(lo + env, buffer1, sizeof(buffer1)),
           print_kib(hi + env, buffer2, sizeof(buffer2)));

    return 0;
}

2014 年 4 月 6 日更新: Linux でのコンパイルと実行が改善されました。Linux では、ls小さな問題と終了ステータス 1 および 2 の重大な問題が区別されますdup()。また、前の 1 MiB から最大 4 MiB の引数スペースをテストします。hi(範囲を変更するには、 inの初期値main()を変更します。)

二分探索に関連するコメントに注意してください。通常、mid ± 1検索の終了を確実にするために使用しますが、この検索は範囲が 1 KiB の場合に終了し、±1 をいじらないと数値が「単純」になります。コンピューターが気にするわけではありませんが、私は気にします。これが、開始範囲が、たとえば 4 KiB .. 1 MiB ではなく 0 MiB .. 1 MiB である理由でもあります。数字をきれいに保ちます。

サンプルラン:

Child: 71822
71822: got E2BIG (7: Argument list too long) at size 524288 (512 KiB)
71821: child 71822 died with exit status 37: success: got E2BIG at size 524288 (512 KiB)
Child: 71823
71823: got E2BIG (7: Argument list too long) at size 262144 (256 KiB)
71821: child 71823 died with exit status 37: success: got E2BIG at size 262144 (256 KiB)
Child: 71824
71821: child 71824 died with exit status 1: command exited with status 1 - it worked at size 131072 (128 KiB)
Child: 71825
71821: child 71825 died with exit status 1: command exited with status 1 - it worked at size 196608 (192 KiB)
Child: 71826
71821: child 71826 died with exit status 1: command exited with status 1 - it worked at size 229376 (224 KiB)
Child: 71827
71821: child 71827 died with exit status 1: command exited with status 1 - it worked at size 245760 (240 KiB)
Child: 71828
71821: child 71828 died with exit status 1: command exited with status 1 - it worked at size 253952 (248 KiB)
Child: 71829
71821: child 71829 died with exit status 1: command exited with status 1 - it worked at size 258048 (252 KiB)
Child: 71830
71830: got E2BIG (7: Argument list too long) at size 260096 (254 KiB)
71821: child 71830 died with exit status 37: success: got E2BIG at size 260096 (254 KiB)
Child: 71831
71821: child 71831 died with exit status 1: command exited with status 1 - it worked at size 259072 (253 KiB)
Environment size = 2124
Best guess: maximum argument size in range 261196 (255 KiB) to 262220 (256 KiB)
于 2013-09-01T15:58:20.600 に答える