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