aboutsummaryrefslogtreecommitdiffstats
path: root/src/tests/userprog/no-vm/multi-oom.c
blob: 6a4472daed7575a319e0ee57cdf26836af34f5bd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/* Recursively executes itself until the child fails to execute.
   We expect that at least 30 copies can run.

   We count how many children your kernel was able to execute
   before it fails to start a new process.  We require that,
   if a process doesn't actually get to start, exec() must
   return -1, not a valid PID.

   We repeat this process 10 times, checking that your kernel
   allows for the same level of depth every time.

   In addition, some processes will spawn children that terminate
   abnormally after allocating some resources.

   Written by Godmar Back <godmar@gmail.com>
 */

#include <debug.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <syscall.h>
#include <random.h>
#include "tests/lib.h"

static const int EXPECTED_DEPTH_TO_PASS = 30;
static const int EXPECTED_REPETITIONS = 10;

const char *test_name = "multi-oom";

enum child_termination_mode { RECURSE, CRASH };

/* Spawn a recursive copy of ourselves, passing along instructions
   for the child. */
static pid_t
spawn_child (int c, enum child_termination_mode mode)
{
  char child_cmd[128];
  snprintf (child_cmd, sizeof child_cmd,
            "%s %d %s", test_name, c, mode == CRASH ? "-k" : "");
  return exec (child_cmd);
}

/* Open a number of files (and fail to close them).
   The kernel must free any kernel resources associated
   with these file descriptors. */
static void
consume_some_resources (void)
{
  int fd, fdmax = 126;

  /* Open as many files as we can, up to fdmax.
     Depending on how file descriptors are allocated inside
     the kernel, open() may fail if the kernel is low on memory.
     A low-memory condition in open() should not lead to the
     termination of the process.  */
  for (fd = 0; fd < fdmax; fd++)
    if (open (test_name) == -1)
      break;
}

/* Consume some resources, then terminate this process
   in some abnormal way.  */
static int NO_INLINE
consume_some_resources_and_die (int seed)
{
  consume_some_resources ();
  random_init (seed);
  int *PHYS_BASE = (int *)0xC0000000;

  switch (random_ulong () % 5)
    {
      case 0:
        *(int *) NULL = 42;

      case 1:
        return *(int *) NULL;

      case 2:
        return *PHYS_BASE;

      case 3:
        *PHYS_BASE = 42;

      case 4:
        open ((char *)PHYS_BASE);
        exit (-1);

      default:
        NOT_REACHED ();
    }
  return 0;
}

/* The first copy is invoked without command line arguments.
   Subsequent copies are invoked with a parameter 'depth'
   that describes how many parent processes preceded them.
   Each process spawns one or multiple recursive copies of
   itself, passing 'depth+1' as depth.

   Some children are started with the '-k' flag, which will
   result in abnormal termination.
 */
int
main (int argc, char *argv[])
{
  int n;

  n = argc > 1 ? atoi (argv[1]) : 0;
  bool is_at_root = (n == 0);
  if (is_at_root)
    msg ("begin");

  /* If -k is passed, crash this process. */
  if (argc > 2 && !strcmp(argv[2], "-k"))
    {
      consume_some_resources_and_die (n);
      NOT_REACHED ();
    }

  int howmany = is_at_root ? EXPECTED_REPETITIONS : 1;
  int i, expected_depth = -1;

  for (i = 0; i < howmany; i++)
    {
      pid_t child_pid;

      /* Spawn a child that will be abnormally terminated.
         To speed the test up, do this only for processes
         spawned at a certain depth. */
      if (n > EXPECTED_DEPTH_TO_PASS/2)
        {
          child_pid = spawn_child (n + 1, CRASH);
          if (child_pid != -1)
            {
              if (wait (child_pid) != -1)
                fail ("crashed child should return -1.");
            }
          /* If spawning this child failed, so should
             the next spawn_child below. */
        }

      /* Now spawn the child that will recurse. */
      child_pid = spawn_child (n + 1, RECURSE);

      /* If maximum depth is reached, return result. */
      if (child_pid == -1)
        return n;

      /* Else wait for child to report how deeply it was able to recurse. */
      int reached_depth = wait (child_pid);
      if (reached_depth == -1)
        fail ("wait returned -1.");

      /* Record the depth reached during the first run; on subsequent
         runs, fail if those runs do not match the depth achieved on the
         first run. */
      if (i == 0)
        expected_depth = reached_depth;
      else if (expected_depth != reached_depth)
        fail ("after run %d/%d, expected depth %d, actual depth %d.",
              i, howmany, expected_depth, reached_depth);
      ASSERT (expected_depth == reached_depth);
    }

  consume_some_resources ();

  if (n == 0)
    {
      if (expected_depth < EXPECTED_DEPTH_TO_PASS)
        fail ("should have forked at least %d times.", EXPECTED_DEPTH_TO_PASS);
      msg ("success. program forked %d times.", howmany);
      msg ("end");
    }

  return expected_depth;
}
// vim: sw=2