Home | History | Annotate | Download | only in pthread_cond_timedwait
      1 /*
      2  * Copyright (c) 2004, Bull S.A..  All rights reserved.
      3  * Created by: Sebastien Decugis
      4 
      5  * This program is free software; you can redistribute it and/or modify it
      6  * under the terms of version 2 of the GNU General Public License as
      7  * published by the Free Software Foundation.
      8  *
      9  * This program is distributed in the hope that it would be useful, but
     10  * WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
     12  *
     13  * You should have received a copy of the GNU General Public License along
     14  * with this program; if not, write the Free Software Foundation, Inc.,
     15  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     16 
     17  * This sample test aims to check the following assertion:
     18  *
     19  * When the function returns successfully, everything is as if
     20  * the thread had locked the mutex.
     21  *
     22 
     23  * The steps are:
     24  * -> For each mutex type;
     25  *   -> with and without process-shared primitive if this is supported;
     26  *   -> with different clocks if this is supported,
     27  * -> Initialize a condvar and a mutex.
     28  * -> Create a new thread (or process for process-shared condvars & mutex)
     29  * -> The new thread (process) locks the mutex, then enters a timedwait which will expire far later.
     30  * -> The parent thread (process) then locks the mutex, ensures that the child is waiting,
     31  *    then signals the condition; and checks the child does not leave the wait function.
     32  * -> The parent unlocks the mutex then waits for the child.
     33  * -> The child checks that it owns the mutex; then it leaves.
     34  */
     35 
     36  /* We are testing conformance to IEEE Std 1003.1, 2003 Edition */
     37 #define _POSIX_C_SOURCE 200112L
     38 
     39  /* We need the XSI extention for the mutex attributes
     40     and the mkstemp() routine */
     41 #ifndef WITHOUT_XOPEN
     42 #define _XOPEN_SOURCE	600
     43 #endif
     44 #include <pthread.h>
     45 #include <stdarg.h>
     46 #include <stdio.h>
     47 #include <stdlib.h>
     48 #include <unistd.h>
     49 
     50 #include <errno.h>
     51 #include <sys/wait.h>
     52 #include <sys/mman.h>
     53 #include <string.h>
     54 #include <time.h>
     55 
     56 #include "../testfrmw/testfrmw.h"
     57 #include "../testfrmw/testfrmw.c"
     58 
     59 #ifndef VERBOSE
     60 #define VERBOSE 1
     61 #endif
     62 
     63 #ifndef WITHOUT_ALTCLK
     64 #define USE_ALTCLK		/* make tests with MONOTONIC CLOCK if supported */
     65 #endif
     66 
     67 #ifndef WITHOUT_XOPEN
     68 
     69 typedef struct {
     70 	pthread_mutex_t mtx;
     71 	pthread_cond_t cnd;
     72 	clockid_t cid;		/* Clock id used by the cond var */
     73 	int type;		/* Mutex type */
     74 	int ctrl;		/* checkpoints */
     75 	int bool;		/* Boolean predicate for the condition */
     76 	int status;		/* error code */
     77 } testdata_t;
     78 
     79 struct _scenar {
     80 	int m_type;		/* Mutex type to use */
     81 	int mc_pshared;		/* 0: mutex and cond are process-private (default) ~ !0: Both are process-shared, if supported */
     82 	int c_clock;		/* 0: cond uses the default clock. ~ !0: Cond uses monotonic clock, if supported. */
     83 	int fork;		/* 0: Test between threads. ~ !0: Test across processes, if supported (mmap) */
     84 	char *descr;		/* Case description */
     85 } scenarii[] = {
     86 	{
     87 	PTHREAD_MUTEX_DEFAULT, 0, 0, 0, "Default mutex"}
     88 	, {
     89 	PTHREAD_MUTEX_NORMAL, 0, 0, 0, "Normal mutex"}
     90 	, {
     91 	PTHREAD_MUTEX_ERRORCHECK, 0, 0, 0, "Errorcheck mutex"}
     92 	, {
     93 	PTHREAD_MUTEX_RECURSIVE, 0, 0, 0, "Recursive mutex"}
     94 
     95 	, {
     96 	PTHREAD_MUTEX_DEFAULT, 1, 0, 0, "PShared default mutex"}
     97 	, {
     98 	PTHREAD_MUTEX_NORMAL, 1, 0, 0, "Pshared normal mutex"}
     99 	, {
    100 	PTHREAD_MUTEX_ERRORCHECK, 1, 0, 0, "Pshared errorcheck mutex"}
    101 	, {
    102 	PTHREAD_MUTEX_RECURSIVE, 1, 0, 0, "Pshared recursive mutex"}
    103 
    104 	, {
    105 	PTHREAD_MUTEX_DEFAULT, 1, 0, 1,
    106 		    "Pshared default mutex across processes"}
    107 	, {
    108 	PTHREAD_MUTEX_NORMAL, 1, 0, 1,
    109 		    "Pshared normal mutex across processes"}
    110 	, {
    111 	PTHREAD_MUTEX_ERRORCHECK, 1, 0, 1,
    112 		    "Pshared errorcheck mutex across processes"}
    113 	, {
    114 	PTHREAD_MUTEX_RECURSIVE, 1, 0, 1,
    115 		    "Pshared recursive mutex across processes"}
    116 
    117 #ifdef USE_ALTCLK
    118 	, {
    119 	PTHREAD_MUTEX_DEFAULT, 1, 1, 1,
    120 		    "Pshared default mutex and alt clock condvar across processes"}
    121 	, {
    122 	PTHREAD_MUTEX_NORMAL, 1, 1, 1,
    123 		    "Pshared normal mutex and alt clock condvar across processes"}
    124 	, {
    125 	PTHREAD_MUTEX_ERRORCHECK, 1, 1, 1,
    126 		    "Pshared errorcheck mutex and alt clock condvar across processes"}
    127 	, {
    128 	PTHREAD_MUTEX_RECURSIVE, 1, 1, 1,
    129 		    "Pshared recursive mutex and alt clock condvar across processes"}
    130 
    131 	, {
    132 	PTHREAD_MUTEX_DEFAULT, 0, 1, 0,
    133 		    "Default mutex and alt clock condvar"}
    134 	, {
    135 	PTHREAD_MUTEX_NORMAL, 0, 1, 0,
    136 		    "Normal mutex and alt clock condvar"}
    137 	, {
    138 	PTHREAD_MUTEX_ERRORCHECK, 0, 1, 0,
    139 		    "Errorcheck mutex and alt clock condvar"}
    140 	, {
    141 	PTHREAD_MUTEX_RECURSIVE, 0, 1, 0,
    142 		    "Recursive mutex and alt clock condvar"}
    143 
    144 	, {
    145 	PTHREAD_MUTEX_DEFAULT, 1, 1, 0,
    146 		    "PShared default mutex and alt clock condvar"}
    147 	, {
    148 	PTHREAD_MUTEX_NORMAL, 1, 1, 0,
    149 		    "Pshared normal mutex and alt clock condvar"}
    150 	, {
    151 	PTHREAD_MUTEX_ERRORCHECK, 1, 1, 0,
    152 		    "Pshared errorcheck mutex and alt clock condvar"}
    153 	, {
    154 	PTHREAD_MUTEX_RECURSIVE, 1, 1, 0,
    155 		    "Pshared recursive mutex and alt clock condvar"}
    156 #endif
    157 };
    158 
    159 void *tf(void *arg)
    160 {
    161 	int ret = 0;
    162 	struct timespec ts;
    163 
    164 	testdata_t *td = (testdata_t *) arg;
    165 
    166 	/* Lock the mutex */
    167 	ret = pthread_mutex_lock(&(td->mtx));
    168 	if (ret != 0) {
    169 		td->status = ret;
    170 		UNRESOLVED(ret, "[child] Unable to lock the mutex");
    171 	}
    172 
    173 	/* Tell the parent the mutex is locked */
    174 	td->ctrl = 1;
    175 
    176 	/* Prepare the timeout parameter */
    177 	ret = clock_gettime(td->cid, &ts);
    178 	if (ret != 0) {
    179 		td->status = ret;
    180 		UNRESOLVED(errno, "[child] Unable get clock time");
    181 	}
    182 
    183 	ts.tv_sec += 10;
    184 #if VERBOSE > 1
    185 	output("[child] Will timeout at %i.%09i\n", ts.tv_sec, ts.tv_nsec);
    186 #endif
    187 
    188 	/* Enter the timed wait */
    189 	do {
    190 		ret = pthread_cond_timedwait(&(td->cnd), &(td->mtx), &ts);
    191 		td->ctrl = 2;
    192 	} while ((ret == 0) && (td->bool == 0));
    193 
    194 	td->ctrl = 3;
    195 
    196 	if (ret != 0) {
    197 		td->status = ret;
    198 		UNRESOLVED(ret, "[child] Cond timedwait returned an error");
    199 	}
    200 
    201 	/* Make sure we are owning the mutex */
    202 	ret = pthread_mutex_trylock(&(td->mtx));
    203 	if (td->type == PTHREAD_MUTEX_RECURSIVE) {
    204 #if VERBOSE > 1
    205 		output
    206 		    ("[child] Recursive mutex. Test if we are able to re-lock.\n");
    207 #endif
    208 		if (ret != 0) {
    209 			td->status = ret;
    210 			FAILED("[child] Unable to relock the recursive mutex");
    211 		}
    212 		ret = pthread_mutex_unlock(&(td->mtx));
    213 		if (ret != 0) {
    214 			td->status = ret;
    215 			UNRESOLVED(ret, "[child] Failed to unlock the mutex");
    216 		}
    217 	} else {		/* This was not a recursive mutex; the call must have failed */
    218 
    219 		if (ret == 0) {
    220 			td->status = -1;
    221 			FAILED
    222 			    ("[child] Thread did not owned the mutex after the timedwait return.");
    223 		}
    224 		if (ret != EBUSY) {
    225 			td->status = ret;
    226 			UNRESOLVED(ret,
    227 				   "[child] Mutex trylock did not return EBUSY");
    228 		}
    229 #if VERBOSE > 1
    230 		output("[child] The mutex was busy (normal).\n");
    231 #endif
    232 	}
    233 
    234 	ret = pthread_mutex_unlock(&(td->mtx));
    235 	if (ret != 0) {
    236 		td->status = ret;
    237 		output("[child] Got error %i: %s\n", ret, strerror(ret));
    238 		FAILED
    239 		    ("[child] Failed to unlock the mutex - owned by another thread?");
    240 	}
    241 
    242 	td->ctrl = 4;
    243 	return NULL;
    244 }
    245 
    246 int main(void)
    247 {
    248 	int ret;
    249 	unsigned int i;
    250 	pthread_mutexattr_t ma;
    251 	pthread_condattr_t ca;
    252 
    253 	testdata_t *td;
    254 	testdata_t alternativ;
    255 
    256 	int do_fork;
    257 
    258 	pid_t child_pr = 0, chkpid;
    259 	int status;
    260 	pthread_t child_th;
    261 
    262 	long pshared, monotonic, cs, mf;
    263 
    264 	output_init();
    265 	pshared = sysconf(_SC_THREAD_PROCESS_SHARED);
    266 	cs = sysconf(_SC_CLOCK_SELECTION);
    267 	monotonic = sysconf(_SC_MONOTONIC_CLOCK);
    268 	mf = sysconf(_SC_MAPPED_FILES);
    269 
    270 #if VERBOSE > 0
    271 	output("Test starting\n");
    272 	output("System abilities:\n");
    273 	output(" TPS : %li\n", pshared);
    274 	output(" CS  : %li\n", cs);
    275 	output(" MON : %li\n", monotonic);
    276 	output(" MF  : %li\n", mf);
    277 	if ((mf < 0) || (pshared < 0))
    278 		output("Process-shared attributes won't be tested\n");
    279 	if ((cs < 0) || (monotonic < 0))
    280 		output("Alternative clock won't be tested\n");
    281 #endif
    282 
    283 	/* We are not interested in testing the clock if we have no other clock available.. */
    284 	if (monotonic < 0)
    285 		cs = -1;
    286 
    287 #ifndef USE_ALTCLK
    288 	if (cs > 0)
    289 		output
    290 		    ("Implementation supports the MONOTONIC CLOCK but option is disabled in test.\n");
    291 #endif
    292 
    293 /**********
    294  * Allocate space for the testdata structure
    295  */
    296 	if (mf < 0) {
    297 		/* Cannot mmap a file, we use an alternative method */
    298 		td = &alternativ;
    299 		pshared = -1;	/* We won't do this testing anyway */
    300 #if VERBOSE > 0
    301 		output("Testdata allocated in the process memory.\n");
    302 #endif
    303 	} else {
    304 		/* We will place the test data in a mmaped file */
    305 		char filename[] = "/tmp/cond_timedwait_3-2-XXXXXX";
    306 		size_t sz;
    307 		void *mmaped;
    308 		int fd;
    309 		char *tmp;
    310 
    311 		/* We now create the temp files */
    312 		fd = mkstemp(filename);
    313 		if (fd == -1) {
    314 			UNRESOLVED(errno,
    315 				   "Temporary file could not be created");
    316 		}
    317 
    318 		/* and make sure the file will be deleted when closed */
    319 		unlink(filename);
    320 
    321 #if VERBOSE > 1
    322 		output("Temp file created (%s).\n", filename);
    323 #endif
    324 
    325 		sz = (size_t) sysconf(_SC_PAGESIZE);
    326 
    327 		tmp = calloc(1, sz);
    328 		if (tmp == NULL) {
    329 			UNRESOLVED(errno, "Memory allocation failed");
    330 		}
    331 
    332 		/* Write the data to the file.  */
    333 		if (write(fd, tmp, sz) != (ssize_t) sz) {
    334 			UNRESOLVED(sz, "Writting to the file failed");
    335 		}
    336 
    337 		free(tmp);
    338 
    339 		/* Now we can map the file in memory */
    340 		mmaped =
    341 		    mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    342 		if (mmaped == MAP_FAILED) {
    343 			UNRESOLVED(errno, "mmap failed");
    344 		}
    345 
    346 		td = (testdata_t *) mmaped;
    347 
    348 		/* Our datatest structure is now in shared memory */
    349 #if VERBOSE > 1
    350 		output("Testdata allocated in shared memory.\n");
    351 #endif
    352 	}
    353 
    354 /**********
    355  * For each test scenario, initialize the attributes and other variables.
    356  */
    357 	for (i = 0; i < (sizeof(scenarii) / sizeof(scenarii[0])); i++) {
    358 #if VERBOSE > 1
    359 		output("[parent] Preparing attributes for: %s\n",
    360 		       scenarii[i].descr);
    361 #endif
    362 		/* set / reset everything */
    363 		do_fork = 0;
    364 		ret = pthread_mutexattr_init(&ma);
    365 		if (ret != 0) {
    366 			UNRESOLVED(ret,
    367 				   "[parent] Unable to initialize the mutex attribute object");
    368 		}
    369 		ret = pthread_condattr_init(&ca);
    370 		if (ret != 0) {
    371 			UNRESOLVED(ret,
    372 				   "[parent] Unable to initialize the cond attribute object");
    373 		}
    374 
    375 		/* Set the mutex type */
    376 		ret = pthread_mutexattr_settype(&ma, scenarii[i].m_type);
    377 		if (ret != 0) {
    378 			UNRESOLVED(ret, "[parent] Unable to set mutex type");
    379 		}
    380 #if VERBOSE > 1
    381 		output("[parent] Mutex type : %i\n", scenarii[i].m_type);
    382 #endif
    383 
    384 		/* Set the pshared attributes, if supported */
    385 		if ((pshared > 0) && (scenarii[i].mc_pshared != 0)) {
    386 			ret =
    387 			    pthread_mutexattr_setpshared(&ma,
    388 							 PTHREAD_PROCESS_SHARED);
    389 			if (ret != 0) {
    390 				UNRESOLVED(ret,
    391 					   "[parent] Unable to set the mutex process-shared");
    392 			}
    393 			ret =
    394 			    pthread_condattr_setpshared(&ca,
    395 							PTHREAD_PROCESS_SHARED);
    396 			if (ret != 0) {
    397 				UNRESOLVED(ret,
    398 					   "[parent] Unable to set the cond var process-shared");
    399 			}
    400 #if VERBOSE > 1
    401 			output("[parent] Mutex & cond are process-shared\n");
    402 #endif
    403 		}
    404 #if VERBOSE > 1
    405 		else {
    406 			output("[parent] Mutex & cond are process-private\n");
    407 		}
    408 #endif
    409 
    410 		/* Set the alternative clock, if supported */
    411 #ifdef USE_ALTCLK
    412 		if ((cs > 0) && (scenarii[i].c_clock != 0)) {
    413 			ret = pthread_condattr_setclock(&ca, CLOCK_MONOTONIC);
    414 			if (ret != 0) {
    415 				UNRESOLVED(ret,
    416 					   "[parent] Unable to set the monotonic clock for the cond");
    417 			}
    418 #if VERBOSE > 1
    419 			output("[parent] Cond uses the Monotonic clock\n");
    420 #endif
    421 		}
    422 #if VERBOSE > 1
    423 		else {
    424 			output("[parent] Cond uses the default clock\n");
    425 		}
    426 #endif
    427 #endif
    428 
    429 		/* Tell whether the test will be across processes */
    430 		if ((pshared > 0) && (scenarii[i].fork != 0)) {
    431 			do_fork = 1;
    432 #if VERBOSE > 1
    433 			output("[parent] Child will be a new process\n");
    434 #endif
    435 		}
    436 #if VERBOSE > 1
    437 		else {
    438 			output("[parent] Child will be a new thread\n");
    439 		}
    440 #endif
    441 
    442 /**********
    443  * Initialize the testdata_t structure with the previously defined attributes
    444  */
    445 		/* Initialize the mutex */
    446 		ret = pthread_mutex_init(&(td->mtx), &ma);
    447 		if (ret != 0) {
    448 			UNRESOLVED(ret, "[parent] Mutex init failed");
    449 		}
    450 
    451 		/* initialize the condvar */
    452 		ret = pthread_cond_init(&(td->cnd), &ca);
    453 		if (ret != 0) {
    454 			UNRESOLVED(ret, "[parent] Cond init failed");
    455 		}
    456 
    457 		/* Initialize the other datas from the test structure */
    458 #ifdef USE_ALTCLK
    459 		ret = pthread_condattr_getclock(&ca, &(td->cid));
    460 		if (ret != 0) {
    461 			UNRESOLVED(ret,
    462 				   "[parent] Unable to read cond clock attribute");
    463 		}
    464 #else
    465 		td->cid = CLOCK_REALTIME;
    466 #endif
    467 
    468 		ret = pthread_mutexattr_gettype(&ma, &(td->type));
    469 		if (ret != 0) {
    470 			UNRESOLVED(ret,
    471 				   "[parent] Unable to read mutex type attribute");
    472 		}
    473 
    474 		td->ctrl = 0;
    475 		td->bool = 0;
    476 		td->status = 0;
    477 
    478 /**********
    479  * Proceed to the actual testing
    480  */
    481 
    482 		/* Create the child */
    483 		if (do_fork != 0) {
    484 			/* We are testing across two processes */
    485 			child_pr = fork();
    486 			if (child_pr == -1) {
    487 				UNRESOLVED(errno, "[parent] Fork failed");
    488 			}
    489 
    490 			if (child_pr == 0) {
    491 #if VERBOSE > 1
    492 				output("[child] Child process starting...\n");
    493 #endif
    494 
    495 				if (tf((void *)td) != NULL) {
    496 					UNRESOLVED(-1,
    497 						   "[child] Got an unexpected return value from test function");
    498 				} else {
    499 					/* We cannot use the PASSED macro here since it would terminate the output */
    500 					exit(0);
    501 				}
    502 			}
    503 			/* Only the parent process goes further */
    504 		} else {	/* do_fork == 0 */
    505 
    506 			/* We are testing across two threads */
    507 			ret = pthread_create(&child_th, NULL, tf, td);
    508 			if (ret != 0) {
    509 				UNRESOLVED(ret,
    510 					   "[parent] Unable to create the child thread.");
    511 			}
    512 		}
    513 
    514 		/* Note: in case of an error, the child process will be alive for 10 sec then exit. */
    515 
    516 		/* Child is now running and will enter the timedwait */
    517 		/* We are waiting for this; and we have to monitor the status value as well. */
    518 		ret = pthread_mutex_lock(&(td->mtx));
    519 		if (ret != 0) {
    520 			UNRESOLVED(ret, "[parent] Unable to lock the mutex");
    521 		}
    522 
    523 		while ((td->ctrl == 0) && (td->status == 0)) {
    524 			ret = pthread_mutex_unlock(&(td->mtx));
    525 			if (ret != 0) {
    526 				UNRESOLVED(ret,
    527 					   "[parent] Unable to unlock the mutex");
    528 			}
    529 			sched_yield();
    530 			ret = pthread_mutex_lock(&(td->mtx));
    531 			if (ret != 0) {
    532 				UNRESOLVED(ret,
    533 					   "[parent] Unable to lock the mutex");
    534 			}
    535 		}
    536 
    537 		if ((td->ctrl == 2) && (td->status == 0)) {	/* Spurious wakeups hapenned */
    538 			output
    539 			    ("Spurious wake ups have happened. Maybe pthread_cond_timedwait is broken?\n");
    540 			td->ctrl = 1;
    541 		}
    542 
    543 		if (td->ctrl == 1) {	/* The child is inside the cond timedwait */
    544 			ret = pthread_cond_signal(&(td->cnd));
    545 			if (ret != 0) {
    546 				UNRESOLVED(ret,
    547 					   "[parent] Unable to signal the condition");
    548 			}
    549 
    550 			/* Let the child leave the wait function if something is broken */
    551 			usleep(100);
    552 
    553 			if (td->ctrl != 1) {
    554 				FAILED
    555 				    ("[parent] Child went out from pthread_cond_timedwait without locking the mutex");
    556 			}
    557 
    558 			/* Allow the child to continue */
    559 			td->bool = 1;
    560 		}
    561 
    562 		/* Let the child do its checking */
    563 		ret = pthread_mutex_unlock(&(td->mtx));
    564 		if (ret != 0) {
    565 			UNRESOLVED(ret, "[parent] Unable to unlock the mutex");
    566 		}
    567 
    568 		/* Wait for the child to terminate */
    569 		if (do_fork != 0) {
    570 			/* We were testing across two processes */
    571 			chkpid = waitpid(child_pr, &status, 0);
    572 			if (chkpid != child_pr) {
    573 				output("Expected pid: %i. Got %i\n",
    574 				       (int)child_pr, (int)chkpid);
    575 				UNRESOLVED(errno, "Waitpid failed");
    576 			}
    577 			if (WIFSIGNALED(status)) {
    578 				output("Child process killed with signal %d\n",
    579 				       WTERMSIG(status));
    580 				UNRESOLVED(td->status,
    581 					   "Child process was killed");
    582 			}
    583 
    584 			if (WIFEXITED(status)) {
    585 				ret = WEXITSTATUS(status);
    586 			} else {
    587 				UNRESOLVED(td->status,
    588 					   "Child process was neither killed nor exited");
    589 			}
    590 
    591 			if (ret != 0) {
    592 				exit(ret);	/* Output has already been closed in child */
    593 			}
    594 		} else {	/* child was a thread */
    595 
    596 			ret = pthread_join(child_th, NULL);
    597 			if (ret != 0) {
    598 				UNRESOLVED(ret,
    599 					   "[parent] Unable to join the thread");
    600 			}
    601 		}
    602 
    603 /**********
    604  * Destroy the data
    605  */
    606 		ret = pthread_cond_destroy(&(td->cnd));
    607 		if (ret != 0) {
    608 			UNRESOLVED(ret, "Failed to destroy the cond var");
    609 		}
    610 
    611 		ret = pthread_mutex_destroy(&(td->mtx));
    612 		if (ret != 0) {
    613 			UNRESOLVED(ret, "Failed to destroy the mutex");
    614 		}
    615 
    616 		ret = pthread_condattr_destroy(&ca);
    617 		if (ret != 0) {
    618 			UNRESOLVED(ret,
    619 				   "Failed to destroy the cond var attribute object");
    620 		}
    621 
    622 		ret = pthread_mutexattr_destroy(&ma);
    623 		if (ret != 0) {
    624 			UNRESOLVED(ret,
    625 				   "Failed to destroy the mutex attribute object");
    626 		}
    627 
    628 	}			/* Proceed to the next scenario */
    629 
    630 #if VERBOSE > 0
    631 	output("Test passed\n");
    632 #endif
    633 
    634 	PASSED;
    635 }
    636 
    637 #else /* WITHOUT_XOPEN */
    638 int main(void)
    639 {
    640 	output_init();
    641 	UNTESTED("This test requires XSI features");
    642 }
    643 #endif
    644