1 /***********************************************************************************************************************************
2 Harness for Testing Using Fork
3 
4 Sometimes it is useful to use a child process for testing.  This can be to test interaction with another process or to avoid
5 polluting memory in the main process with something that can't easily be undone.
6 
7 The general form of the fork harness is:
8 
9 // Parameters may be passed, see HrnForkParam.
10 HRN_FORK_BEGIN()
11 {
12     // This block is required and can be repeated up to HRN_FORK_CHILD_MAX times. Parameters may be passed, see HrnForkChildParam.
13     HRN_FORK_CHILD_BEGIN()
14     {
15         // Child test code goes here
16     }
17     HRN_FORK_CHILD_END();
18 
19     // This block is optional but generally useful. Parameters may be passed, see HrnForkParentParam.
20     HRN_FORK_PARENT_BEGIN()
21     {
22         // Parent test code goes here
23     }
24     HRN_FORK_PARENT_END();
25 }
26 HRN_FORK_END();
27 
28 If the child process does not explicitly exit in HRN_FORK_CHILD_BEGIN/END() then it will exit with 0 at HRN_FORK_END(). This harness
29 is not intended for long-lived child processes.
30 
31 There should not be any code outside the HRN_FORK_CHILD_BEGIN/END() and HRN_FORK_PARENT_BEGIN/END() blocks.
32 ***********************************************************************************************************************************/
33 #include <sys/wait.h>
34 #include <unistd.h>
35 
36 #include <common/harnessLog.h>
37 
38 /***********************************************************************************************************************************
39 Define the max number of child processes allowed
40 ***********************************************************************************************************************************/
41 #define HRN_FORK_CHILD_MAX                                          4
42 
43 /***********************************************************************************************************************************
44 Default timeout for IoRead/IoWrite interfaces
45 ***********************************************************************************************************************************/
46 #define HRN_FORK_TIMEOUT                                            2000
47 
48 /***********************************************************************************************************************************
49 Total number of child processes forked
50 ***********************************************************************************************************************************/
51 #define HRN_FORK_PROCESS_TOTAL()                                                                                                   \
52     HRN_FORK_processTotal
53 
54 /***********************************************************************************************************************************
55 Return the process index of the child (i.e. the index in the total)
56 ***********************************************************************************************************************************/
57 #define HRN_FORK_PROCESS_IDX()                                                                                                     \
58     HRN_FORK_processIdx
59 
60 /***********************************************************************************************************************************
61 Return the id of the child process, 0 if in the child process
62 ***********************************************************************************************************************************/
63 #define HRN_FORK_PROCESS_ID(processIdx)                                                                                            \
64     HRN_FORK_processIdList[processIdx]
65 
66 /***********************************************************************************************************************************
67 Return the pipe for the child process
68 ***********************************************************************************************************************************/
69 #define HRN_FORK_PIPE(processIdx)                                                                                                  \
70     HRN_FORK_pipe[processIdx]
71 
72 /***********************************************************************************************************************************
73 Get read/write pipe file descriptors
74 ***********************************************************************************************************************************/
75 #define HRN_FORK_CHILD_READ_FD()                                                                                                   \
76     HRN_FORK_PIPE(HRN_FORK_PROCESS_IDX())[1][0]
77 
78 #define HRN_FORK_CHILD_WRITE_FD()                                                                                                  \
79     HRN_FORK_PIPE(HRN_FORK_PROCESS_IDX())[0][1]
80 
81 #define HRN_FORK_PARENT_READ_FD(processIdx)                                                                                        \
82     HRN_FORK_PIPE(processIdx)[0][0]
83 
84 #define HRN_FORK_PARENT_WRITE_FD(processIdx)                                                                                       \
85     HRN_FORK_PIPE(processIdx)[1][1]
86 
87 /***********************************************************************************************************************************
88 Get IoRead/IoWrite interfaces. These are automatically created at fork time and are available via these macros(). Since
89 IoRead/IoWrite buffer internally using both the interfaces and the file descriptors will be unpredictable unless the IoRead/IoWrite
90 buffers are known to be empty, e.g. ioWriteFlush() has been called.
91 ***********************************************************************************************************************************/
92 #ifdef HRN_FEATURE_IO
93     #define HRN_FORK_CHILD_READ()                                                                                                  \
94         HRN_FORK_ioReadChild
95 
96     #define HRN_FORK_CHILD_WRITE()                                                                                                 \
97         HRN_FORK_ioWriteChild
98 
99     #define HRN_FORK_PARENT_READ(processIdx)                                                                                       \
100         HRN_FORK_ioReadParent[processIdx]
101 
102     #define HRN_FORK_PARENT_WRITE(processIdx)                                                                                      \
103         HRN_FORK_ioWriteParent[processIdx]
104 #endif
105 
106 /***********************************************************************************************************************************
107 Get/put notify messages. These macros allow the parent and child process to synchronize which is useful, e.g. releasing locks.
108 ***********************************************************************************************************************************/
109 #ifdef HRN_FEATURE_IO
110     // General notify get macro used by parent/child
111     #define HRN_FORK_NOTIFY_GET(read)                                                                                              \
112         ioReadLine(read)
113 
114     // General notify put macro used by parent/child
115     #define HRN_FORK_NOTIFY_PUT(write)                                                                                             \
116         do                                                                                                                         \
117         {                                                                                                                          \
118             ioWriteStrLine(write, EMPTY_STR);                                                                                      \
119             ioWriteFlush(write);                                                                                                   \
120         }                                                                                                                          \
121         while (0)
122 
123     // Put notification to parent from child
124     #define HRN_FORK_CHILD_NOTIFY_GET()                                                                                            \
125         HRN_FORK_NOTIFY_GET(HRN_FORK_CHILD_READ())
126 
127     // Get notification from parent to child
128     #define HRN_FORK_CHILD_NOTIFY_PUT()                                                                                            \
129         HRN_FORK_NOTIFY_PUT(HRN_FORK_CHILD_WRITE())
130 
131     // Put notification to child from parent
132     #define HRN_FORK_PARENT_NOTIFY_GET(processIdx)                                                                                 \
133         HRN_FORK_NOTIFY_GET(HRN_FORK_PARENT_READ(processIdx))
134 
135     // Get notification from child to parent
136     #define HRN_FORK_PARENT_NOTIFY_PUT(processIdx)                                                                                 \
137         HRN_FORK_NOTIFY_PUT(HRN_FORK_PARENT_WRITE(processIdx))
138 #endif
139 
140 /***********************************************************************************************************************************
141 At the end of the HRN_FORK block the parent will wait for the child to exit.  By default an exit code of 0 is expected but that can
142 be modified when the child begins.
143 ***********************************************************************************************************************************/
144 #define HRN_FORK_CHILD_EXPECTED_EXIT_STATUS(processIdx)                                                                            \
145     HRN_FORK_expectedExitStatus[processIdx]
146 
147 /***********************************************************************************************************************************
148 Begin the fork block
149 ***********************************************************************************************************************************/
150 typedef struct HrnForkParam
151 {
152     VAR_PARAM_HEADER;
153 
154     // Timeout in ms for IoRead/IoWrite interfaces (defaults to HRN_FORK_TIMEOUT). May be overridden in HRN_FORK_CHILD_BEGIN() or
155     // HRN_FORK_PARENT_BEGIN().
156     uint64_t timeout;
157 } HrnForkParam;
158 
159 #define HRN_FORK_BEGIN(...)                                                                                                        \
160     do                                                                                                                             \
161     {                                                                                                                              \
162         HrnForkParam param = {VAR_PARAM_INIT, __VA_ARGS__};                                                                        \
163                                                                                                                                    \
164         /* Set timeout default */                                                                                                  \
165         if (param.timeout == 0)                                                                                                    \
166             param.timeout = HRN_FORK_TIMEOUT;                                                                                      \
167                                                                                                                                    \
168         unsigned int HRN_FORK_PROCESS_TOTAL() = 0;                                                                                 \
169         pid_t HRN_FORK_PROCESS_ID(HRN_FORK_CHILD_MAX) = {0};                                                                       \
170         int HRN_FORK_CHILD_EXPECTED_EXIT_STATUS(HRN_FORK_CHILD_MAX) = {0};                                                         \
171         int HRN_FORK_PIPE(HRN_FORK_CHILD_MAX)[2][2] = {{{0}}};
172 
173 /***********************************************************************************************************************************
174 Create a child process
175 ***********************************************************************************************************************************/
176 typedef struct HrnForkChildParam
177 {
178     VAR_PARAM_HEADER;
179 
180     // Expected exit status. Defaults to 0. This does not need to be changed unless the child is expected to exit with a non-zero
181     // code for testing purposes.
182     int expectedExitStatus;
183 
184     // Prefix used to name IoRead/IoWrite interfaces. Defaults to "child" so the default name is "child [idx] read/write".
185     const char *prefix;
186 
187     // Timeout in ms for IoRead/IoWrite interfaces. Defaults to the value passed in HRN_FORK_BEGIN().
188     uint64_t timeout;
189 } HrnForkChildParam;
190 
191 // Declare/assign IoRead/IoWrite
192 #ifdef HRN_FEATURE_IO
193     #include "common/io/fdRead.h"
194     #include "common/io/fdWrite.h"
195     #include "common/type/string.h"
196 
197     #define HRN_FORK_CHILD_IO()                                                                                                    \
198         IoRead *HRN_FORK_CHILD_READ() = ioFdReadNewOpen(                                                                           \
199             strNewFmt("%s %u read", paramChild.prefix, HRN_FORK_PROCESS_IDX()), HRN_FORK_CHILD_READ_FD(), paramChild.timeout);     \
200         (void)HRN_FORK_CHILD_READ();                                                                                               \
201         IoWrite *HRN_FORK_CHILD_WRITE() = ioFdWriteNewOpen(                                                                        \
202             strNewFmt("%s %u write", paramChild.prefix, HRN_FORK_PROCESS_IDX()), HRN_FORK_CHILD_WRITE_FD(), paramChild.timeout);   \
203         (void)HRN_FORK_CHILD_WRITE()
204 #else
205     #define HRN_FORK_CHILD_IO()
206 #endif
207 
208 #define HRN_FORK_CHILD_BEGIN(...)                                                                                                  \
209     do                                                                                                                             \
210     {                                                                                                                              \
211         HrnForkChildParam paramChild = {VAR_PARAM_INIT, __VA_ARGS__};                                                              \
212                                                                                                                                    \
213         /* Set prefix default */                                                                                                   \
214         if (paramChild.prefix == NULL)                                                                                             \
215             paramChild.prefix = "child";                                                                                           \
216                                                                                                                                    \
217         /* Set timeout default */                                                                                                  \
218         if (paramChild.timeout == 0)                                                                                               \
219             paramChild.timeout = param.timeout;                                                                                    \
220                                                                                                                                    \
221         HRN_FORK_CHILD_EXPECTED_EXIT_STATUS(HRN_FORK_PROCESS_TOTAL()) = paramChild.expectedExitStatus;                             \
222                                                                                                                                    \
223         /* Create pipe for parent/child communication */                                                                           \
224         THROW_ON_SYS_ERROR_FMT(                                                                                                    \
225             pipe(HRN_FORK_PIPE(HRN_FORK_PROCESS_TOTAL())[0]) == -1, KernelError,                                                   \
226             "unable to create read pipe for child process %u", HRN_FORK_PROCESS_TOTAL());                                          \
227         THROW_ON_SYS_ERROR_FMT(                                                                                                    \
228             pipe(HRN_FORK_PIPE(HRN_FORK_PROCESS_TOTAL())[1]) == -1, KernelError,                                                   \
229             "unable to create write pipe for child process %u", HRN_FORK_PROCESS_TOTAL());                                         \
230                                                                                                                                    \
231         /* Fork child process */                                                                                                   \
232         HRN_FORK_PROCESS_ID(HRN_FORK_PROCESS_TOTAL()) = fork();                                                                    \
233                                                                                                                                    \
234         if (HRN_FORK_PROCESS_ID(HRN_FORK_PROCESS_TOTAL()) == 0)                                                                    \
235         {                                                                                                                          \
236             unsigned int HRN_FORK_PROCESS_IDX() = HRN_FORK_PROCESS_TOTAL();                                                        \
237                                                                                                                                    \
238             /* Declare/assign IoRead/IoWrite */                                                                                    \
239             HRN_FORK_CHILD_IO();                                                                                                   \
240                                                                                                                                    \
241             /* Change log process id to aid in debugging */                                                                        \
242             hrnLogProcessIdSet(HRN_FORK_PROCESS_IDX() + 1);                                                                        \
243                                                                                                                                    \
244             /* Close parent side of pipe */                                                                                        \
245             close(HRN_FORK_PARENT_READ_FD(HRN_FORK_PROCESS_IDX()));                                                                \
246             close(HRN_FORK_PARENT_WRITE_FD(HRN_FORK_PROCESS_IDX()));                                                               \
247 
248 #define HRN_FORK_CHILD_END()                                                                                                       \
249             /* Close child side of pipe */                                                                                         \
250             close(HRN_FORK_CHILD_READ_FD());                                                                                       \
251             close(HRN_FORK_CHILD_WRITE_FD());                                                                                      \
252                                                                                                                                    \
253             exit(0);                                                                                                               \
254         }                                                                                                                          \
255                                                                                                                                    \
256         HRN_FORK_PROCESS_TOTAL()++;                                                                                                \
257     }                                                                                                                              \
258     while (0)
259 
260 /***********************************************************************************************************************************
261 Process in the parent
262 ***********************************************************************************************************************************/
263 typedef struct HrnForkParentParam
264 {
265     VAR_PARAM_HEADER;
266 
267     // Prefix used to name IoRead/IoWrite interfaces. Defaults to "parent" so the default name is "parent [idx] read/write".
268     const char *prefix;
269 
270     // Timeout in ms for IoRead/IoWrite interfaces. Defaults to the value passed in HRN_FORK_BEGIN().
271     uint64_t timeout;
272 } HrnForkParentParam;
273 
274 // Declare IoRead/IoWrite
275 #ifdef HRN_FEATURE_IO
276     #define HRN_FORK_PARENT_IO_DECLARE()                                                                                           \
277         IoRead *HRN_FORK_PARENT_READ(HRN_FORK_CHILD_MAX) = {0};                                                                    \
278         (void)HRN_FORK_PARENT_READ(0);                                                                                             \
279         IoWrite *HRN_FORK_PARENT_WRITE(HRN_FORK_CHILD_MAX) = {0};                                                                  \
280         (void)HRN_FORK_PARENT_WRITE(0)
281 
282     #define HRN_FORK_PARENT_IO_ASSIGN(processIdx)                                                                                  \
283         HRN_FORK_PARENT_READ(processIdx) = ioFdReadNewOpen(                                                                        \
284             strNewFmt("%s %u read", paramParent.prefix, processIdx), HRN_FORK_PARENT_READ_FD(processIdx), paramParent.timeout);    \
285         HRN_FORK_PARENT_WRITE(processIdx) = ioFdWriteNewOpen(                                                                      \
286             strNewFmt("%s %u write", paramParent.prefix, processIdx), HRN_FORK_PARENT_WRITE_FD(processIdx), paramParent.timeout)
287 #else
288     #define HRN_FORK_PARENT_IO_DECLARE()
289     #define HRN_FORK_PARENT_IO_ASSIGN(processIdx)
290 #endif
291 
292 #define HRN_FORK_PARENT_BEGIN(...)                                                                                                 \
293     do                                                                                                                             \
294     {                                                                                                                              \
295         HrnForkParentParam paramParent = {VAR_PARAM_INIT, __VA_ARGS__};                                                            \
296                                                                                                                                    \
297         /* Set prefix default */                                                                                                   \
298         if (paramParent.prefix == NULL)                                                                                            \
299             paramParent.prefix = "parent";                                                                                         \
300                                                                                                                                    \
301         /* Set timeout default */                                                                                                  \
302         if (paramParent.timeout == 0)                                                                                              \
303             paramParent.timeout = param.timeout;                                                                                   \
304                                                                                                                                    \
305         /* Declare IoRead/IoWrite */                                                                                               \
306         HRN_FORK_PARENT_IO_DECLARE();                                                                                              \
307                                                                                                                                    \
308         for (unsigned int processIdx = 0; processIdx < HRN_FORK_PROCESS_TOTAL(); processIdx++)                                     \
309         {                                                                                                                          \
310             /* Close child side of pipe */                                                                                         \
311             close(HRN_FORK_PIPE(processIdx)[1][0]);                                                                                \
312             close(HRN_FORK_PIPE(processIdx)[0][1]);                                                                                \
313                                                                                                                                    \
314             /* Assign IoRead/IoWrite */                                                                                            \
315             HRN_FORK_PARENT_IO_ASSIGN(processIdx);                                                                                 \
316         }
317 
318 #define HRN_FORK_PARENT_END()                                                                                                      \
319         for (unsigned int processIdx = 0; processIdx < HRN_FORK_PROCESS_TOTAL(); processIdx++)                                     \
320         {                                                                                                                          \
321             /* Close parent side of pipe */                                                                                        \
322             close(HRN_FORK_PARENT_READ_FD(processIdx));                                                                            \
323             close(HRN_FORK_PARENT_WRITE_FD(processIdx));                                                                           \
324         }                                                                                                                          \
325     }                                                                                                                              \
326     while (0)
327 
328 /***********************************************************************************************************************************
329 End the fork block and check exit status for all child processes
330 ***********************************************************************************************************************************/
331 #define HRN_FORK_END()                                                                                                             \
332         for (unsigned int processIdx = 0; processIdx < HRN_FORK_PROCESS_TOTAL(); processIdx++)                                     \
333         {                                                                                                                          \
334             int processStatus;                                                                                                     \
335                                                                                                                                    \
336             if (waitpid(HRN_FORK_PROCESS_ID(processIdx), &processStatus, 0) != HRN_FORK_PROCESS_ID(processIdx))                    \
337                 THROW_SYS_ERROR_FMT(AssertError, "unable to find child process %u", processIdx);                                   \
338                                                                                                                                    \
339             if (WEXITSTATUS(processStatus) != HRN_FORK_CHILD_EXPECTED_EXIT_STATUS(processIdx))                                     \
340             {                                                                                                                      \
341                 THROW_FMT(                                                                                                         \
342                     AssertError, "child %u exited with error %d but expected %d", processIdx, WEXITSTATUS(processStatus),          \
343                     HRN_FORK_CHILD_EXPECTED_EXIT_STATUS(processIdx));                                                              \
344             }                                                                                                                      \
345         }                                                                                                                          \
346     }                                                                                                                              \
347     while (0)
348