Loading...
  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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
/* 
 * xnu_quick_test - this tool will do a quick test of every (well, to be
 * honest most) system calls we support in xnu.
 *
 * WARNING - this is not meant to be a full regression test of all the
 * system calls.  The intent is to have a quick test of each system call that
 * can be run very easily and quickly when a new kerenl is built.
 *
 * This tool is meant to grow as we find xnu problems that could have be
 * caught before we submit to a build train.  So please add more tests and
 * make the existing ones better.  Some of the original tests are nothing
 * more than place holders and quite lame.  Just keep in mind that the tool
 * should run as fast as possible.  If it gets too slow then most people
 * will stop running it.
 *
 * LP64 testing tip - when adding or modifying tests, keep in mind the
 * variants in the LP64 world.  If xnu gets passed a structure the varies in
 * size between 32 and 64-bit processes, try to test that a field in the 
 * sructure contains valid data.  For example if we know foo structure
 * looks like:
 * struct foo {
 *		int		an_int;
 *		long	a_long;
 *		int		another_int;
 * }
 * And if we know what another_int should contain then test for the known
 * value since it's offset will vary depending on whether the calling process
 * is 32 or 64 bits.
 *
 * NOTE - we have several workarounds and test exceptions for some
 * outstanding bugs in xnu.  All the workarounds are marked with "todo" and
 * some comments noting the radar number of the offending bug.  Do a seach
 * for "todo" in the source files for this project to locate which tests have
 * known failures.   And please tag any new exceptions you find with "todo"
 * in the comment and the radar number of the bug.
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <grp.h>
#include <unistd.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/syslimits.h>
#include <sys/types.h>
#include <sys/ucred.h>
#include <sys/uio.h>
#include <mach-o/ldsyms.h>
#include <mach-o/loader.h>
#include <mach-o/arch.h>
#include "tests.h"

#if !TARGET_OS_EMBEDDED
#include <XILog/XILog.h>
#endif



/* our table of tests to run  */
struct test_entry   g_tests[] =
{
	{1, &syscall_test, NULL, "syscall"},
	{1, &fork_wait4_exit_test, NULL, "fork, wait4, exit"},
	{1, &read_write_test, NULL, "fsync, ftruncate, lseek, pread, pwrite, read, readv, truncate, write, writev"},
	{1, &open_close_test, NULL, "close, fpathconf, fstat, open, pathconf"},
	{1, &link_stat_unlink_test, NULL, "link, stat, unlink"},
	{1, &chdir_fchdir_test, NULL, "chdir, fchdir"},
	{1, &access_chmod_fchmod_test, NULL, "access, chmod, fchmod"},
	{1, &chown_fchown_lchown_lstat_symlink_test, NULL, "chown, fchown, lchown, lstat, readlink, symlink"},
	{1, &fs_stat_tests, NULL, "fstatfs, getfsstat, statfs, fstatfs64, getfsstat64, statfs64"},
#if !TARGET_OS_EMBEDDED
	{1, &statfs_32bit_inode_tests, NULL, "32-bit inode versions: fstatfs, getfsstat, statfs"},
#endif
	{1, &getpid_getppid_pipe_test, NULL, "getpid, getppid, pipe"},
	{1, &uid_tests, NULL, "getauid, gettid, getuid, geteuid, issetugid, setaudit_addr, seteuid, settid, settid_with_pid, setuid"},
	{1, &mkdir_rmdir_umask_test, NULL, "mkdir, rmdir, umask"},
	{1, &mknod_sync_test, NULL, "mknod, sync"},
	{1, &socket2_tests, NULL, "fsync, getsockopt, poll, select, setsockopt, socketpair"},
	{1, &socket_tests, NULL, "accept, bind, connect, getpeername, getsockname, listen, socket, recvmsg, sendmsg, sendto, sendfile"},
	{1, &chflags_fchflags_test, NULL, "chflags, fchflags"},
	{1, &execve_kill_vfork_test, NULL, "kill, vfork, execve, posix_spawn"},
	{1, &groups_test, NULL, "getegid, getgid, getgroups, setegid, setgid, setgroups"},
	{1, &dup_test, NULL, "dup, dup2, getdtablesize"},
	{1, &getrusage_test, NULL, "getrusage"},
	{1, &signals_test, NULL, "getitimer, setitimer, sigaction, sigpending, sigprocmask, sigsuspend, sigwait"},
	{1, &acct_test, NULL, "acct"},
	{1, &ioctl_test, NULL, "ioctl"},
	{1, &chroot_test, NULL, "chroot"},
	{1, &memory_tests, NULL, "madvise, mincore, minherit, mlock, mlock, mmap, mprotect, msync, munmap"},
	{1, &process_group_test, NULL, "getpgrp, getpgid, getsid, setpgid, setpgrp, setsid"},
	{1, &fcntl_test, NULL, "fcntl"},
	{1, &getlogin_setlogin_test, NULL, "getlogin, setlogin"},
	{1, &getpriority_setpriority_test, NULL, "getpriority, setpriority"},
	{1, &time_tests, NULL, "futimes, gettimeofday, settimeofday, utimes"},
	{1, &rename_test, NULL, "rename, stat"},
	{1, &locking_test, NULL, "flock"},
	{1, &mkfifo_test, NULL, "mkfifo, read, write"},
	{1, &quotactl_test, NULL, "quotactl"},
	{1, &limit_tests, NULL, "getrlimit, setrlimit"},
	{1, &directory_tests, NULL, "getattrlist, getdirentriesattr, setattrlist"},
#if !TARGET_OS_EMBEDDED
	{1, &getdirentries_test, NULL, "getdirentries"},
#endif
	{1, &exchangedata_test, NULL, "exchangedata"},
	{1, &searchfs_test, NULL, "searchfs"},
	{1, &sema2_tests, NULL, "sem_close, sem_open, sem_post, sem_trywait, sem_unlink, sem_wait"},
	{1, &sema_tests, NULL, "semctl, semget, semop"},
	{1, &bsd_shm_tests, NULL, "shm_open, shm_unlink"},
	{1, &shm_tests, NULL, "shmat, shmctl, shmdt, shmget"},
	{1, &xattr_tests, NULL, "fgetxattr, flistxattr, fremovexattr, fsetxattr, getxattr, listxattr, removexattr, setxattr"},
	{1, &aio_tests, NULL, "aio_cancel, aio_error, aio_read, aio_return, aio_suspend, aio_write, fcntl, lio_listio"},
	{1, &kqueue_tests, NULL, "kevent, kqueue"},
	{1, &message_queue_tests, NULL, "msgctl, msgget, msgrcv, msgsnd"},
	{1, &data_exec_tests, NULL, "data/stack execution"},
	{1, &machvm_tests, NULL, "Mach VM calls"},
	{0, NULL, NULL, "last one"}
};

static void create_target_directory( const char * the_targetp );
static void list_all_tests( void );
static void mark_tests_to_run( long my_start, long my_end );
static int parse_tests_to_run( int argc, const char * argv[], int * indexp );
static void usage( void );
static int setgroups_if_single_user(void);
static const char *current_arch( void );

/* globals */
long		g_max_failures = 0;
int		g_skip_setuid_tests = 0;
int		g_xilog_active = 0;
const char *	g_cmd_namep;
char		g_target_path[ PATH_MAX ];
int		g_is_under_rosetta = 0;
int		g_is_single_user = 0;
 
int main( int argc, const char * argv[] ) 
{
	int				my_tests_count, i;
	int				err;
	int				my_failures = 0;
	int				list_the_tests = 0;
	const char *	my_targetp;
	time_t			my_start_time, my_end_time;
	struct stat		my_stat_buf;
	char			my_buffer[64];
	/* vars for XILog */
#if !TARGET_OS_EMBEDDED
	XILogRef		logRef;
	char			*logPath = "";
	char			*config = NULL;
	int				echo = 0;
	int				xml = 0;
#endif
	sranddev( );				/* set up seed for our random name generator */
	g_cmd_namep = argv[0];
	
	/* NOTE - code in create_target_directory will append '/' if it is necessary */
	my_targetp = getenv("TMPDIR");
	if ( my_targetp == NULL )
		my_targetp = "/tmp";
	
	/* make sure our executable is owned by root and has set uid bit */
	err = stat( g_cmd_namep, &my_stat_buf );
	if ( err != 0 ) {
		err = errno;
		printf( "stat call on our executable failed -  \"%s\" \n", g_cmd_namep );
		printf( " failed with error %d - \"%s\" \n", err, strerror( err) );
		exit( -1 );
	}
	if ( my_stat_buf.st_uid != 0 || (my_stat_buf.st_mode & S_ISUID) == 0 ) {
		printf( "executable file -  \"%s\" \n", g_cmd_namep );
		printf( "does not have correct owner (must be root) or setuid bit is not set \n" );
		exit( -1 );
	}
	
	/* parse input parameters */
	for ( i = 1; i < argc; i++ ) {
		if ( strcmp( argv[i], "-u" ) == 0 ) {
			usage( );
		}
		if ( strcmp( argv[i], "-t" ) == 0 ||
			 strcmp( argv[i], "-target" ) == 0 ) {
			if ( ++i >= argc ) {
				printf( "invalid target parameter \n" );
				usage( );
			}
			/* verify our target directory exists */
			my_targetp = argv[i];
			err = stat( my_targetp, &my_stat_buf );
			if ( err != 0 || S_ISDIR(my_stat_buf.st_mode) == 0 ) {
				printf( "invalid target path \n" );
				if ( err != 0 ) {
					printf( "stat call failed with error %d - \"%s\" \n", errno, strerror( errno) );
				}
				usage( );
			}
			continue;
		}
		if ( strcmp( argv[i], "-f" ) == 0 ||
			 strcmp( argv[i], "-failures" ) == 0 ) {
			if ( ++i >= argc ) {
				printf( "invalid failures parameter \n" );
				usage( );
			}

			/* get our max number of failures */
			g_max_failures = strtol( argv[i], NULL, 10 );
			continue;
		}
		if ( strcmp( argv[i], "-l" ) == 0 ||
			 strcmp( argv[i], "-list" ) == 0 ) {
			/* list all the tests this tool will do.
			 */
			list_the_tests = 1;
			continue;
		}
		if ( strcmp( argv[i], "-r" ) == 0 ||
			 strcmp( argv[i], "-run" ) == 0 ) {
			if ( ++i >= argc ) {
				printf( "invalid run tests parameter \n" );
				usage( );
			}

			/* get which tests to run */
			if ( parse_tests_to_run( argc, argv, &i ) != 0 ) {
				printf( "invalid run tests parameter \n" );
				usage( );
			}
			continue;
		}
		if ( strcmp( argv[i], "-s" ) == 0 ||
			 strcmp( argv[i], "-skip" ) == 0 ) {
			/* set that want to skip the setuid related tests - this is useful for debgugging since since I can't  
			 * get setuid tests to work while in gdb.
			 */
			g_skip_setuid_tests = 1;
			continue;
		}
#if !TARGET_OS_EMBEDDED	
		if ( strcmp( argv[i], "-x" ) == 0 ||
			 strcmp( argv[i], "-xilog" ) == 0 ) {
			g_xilog_active = 1;
			continue;
		}
#endif
		printf( "invalid argument \"%s\" \n", argv[i] );
		usage( );
	}

	/* done parsing.
	 */
    
#ifdef __ppc__
	/* determine if we are running under Rosetta 
	 */
	{
	    int val = 0;
	    size_t size = sizeof val;
	    if (sysctlbyname("sysctl.proc_native", &val, &size, NULL, 0) == -1)
		g_is_under_rosetta = 0;
	    else
		g_is_under_rosetta = val ? 0 : 1;
	}
#endif

	/* Populate groups list if we're in single user mode */
	if (setgroups_if_single_user()) {
		return 1;
	}
    
	if ( list_the_tests != 0 ) {
		list_all_tests( );
		return 0;
	}
#if !TARGET_OS_EMBEDDED
	if (g_xilog_active == 1) {	
		logRef = XILogOpenLogExtended( logPath, "xnu_quick_test", "com.apple.coreos", 
										config, xml, echo, NULL, "ResultOwner", 
										"com.apple.coreos", NULL );
		if( logRef == NULL )  {
			fprintf(stderr,"Couldn't create log: %s",logPath);
			exit(-1);
		}
	}
#endif
	
	/* build a test target directory that we use as our path to create any test
	 * files and directories.
	 */
	create_target_directory( my_targetp );
	printf( "Will allow %ld failures before testing is aborted \n", g_max_failures );
	
        if (g_is_under_rosetta) {
                printf("Running under Rosetta.\n");
        }
        
	my_start_time = time( NULL );
	printf( "\nBegin testing - %s \n", ctime_r( &my_start_time, &my_buffer[0] ) );
	printf( "Current architecture is %s\n", current_arch() );

	/* run each test that is marked to run in our table until we complete all of them or
	 * hit the maximum number of failures.
	 */
	my_tests_count = (sizeof( g_tests ) / sizeof( g_tests[0] ));
	for ( i = 0; i < (my_tests_count - 1); i++ ) {
		int				my_err;
		test_entryp		my_testp;

		my_testp = &g_tests[i];
		if ( my_testp->test_run_it == 0 || my_testp->test_routine == NULL )
			continue;
#if !TARGET_OS_EMBEDDED	
		if (g_xilog_active == 1) {	
			XILogBeginTestCase( logRef, my_testp->test_infop, my_testp->test_infop );	
			XILogMsg( "test #%d - %s \n", (i + 1), my_testp->test_infop );
		}
#endif
		printf( "test #%d - %s \n", (i + 1), my_testp->test_infop );
		my_err = my_testp->test_routine( my_testp->test_input );
		if ( my_err != 0 ) {
			printf("\t--> FAILED \n");
#if !TARGET_OS_EMBEDDED	
			if (g_xilog_active == 1) {	
				XILogMsg("SysCall %s failed", my_testp->test_infop);
				XILogErr("Result %d", my_err);
			}
#endif
			my_failures++;
			if ( my_failures > g_max_failures ) {
#if !TARGET_OS_EMBEDDED	
				if (g_xilog_active == 1) {	
					XILogEndTestCase( logRef, kXILogTestPassOnErrorLevel );
				}
#endif
				printf( "\n too many failures - test aborted \n" );
				goto exit_this_routine;
			}
		}
#if !TARGET_OS_EMBEDDED	
		if (g_xilog_active == 1) {	
			XILogEndTestCase(logRef, kXILogTestPassOnErrorLevel);
		}
#endif
	}
	
exit_this_routine:
	my_end_time = time( NULL );
	printf( "\nEnd testing - %s \n", ctime_r( &my_end_time, &my_buffer[0] ) );

	/* clean up our test directory */
	rmdir( &g_target_path[0] );	

#if !TARGET_OS_EMBEDDED	
	if (g_xilog_active == 1) {	
		XILogCloseLog(logRef);
	}
#endif
	
    return 0;
} /* main */


/* 
 * parse_tests_to_run - parse the -run argument parameters.  the run argument tells us which tests the user
 * wants to run.  we accept ranges (example  1 - 44) and runs of tests (example 2, 33, 34, 100) or a mix of
 * both (example  1, 44 - 100, 200, 250)
 */
static int parse_tests_to_run( int argc, const char * argv[], int * indexp )
{
	int				my_tests_count, is_range = 0, i;
	const char *	my_ptr;
	char *			my_temp_ptr;
	long			my_first_test_number, my_last_test_number;
	char			my_buffer[ 128 ];
	
	/* set tests table to not run any tests then go back and set the specific tests the caller asked for */
	my_tests_count = (sizeof( g_tests ) / sizeof( g_tests[0] ));
	for ( i = 0; i < (my_tests_count - 1); i++ ) {
		g_tests[ i ].test_run_it = 0;
	}
	 
	for ( i = *indexp; i < argc; i++ ) {
		my_ptr = argv[ i ];
		if ( strlen( my_ptr ) > 1 && *my_ptr == '-' && isalpha( *(my_ptr + 1) ) ) {
			/* we have hit a new argument - need to make sure caller uses this argument on the next
			 * pass through its parse loop (which will bump the index value so we want to be one less
			 * than the actual index). 
			 */
			*indexp = (i - 1);
			return 0;
		}
		
		if ( strlen( my_ptr ) == 1 && *my_ptr == '-' ) {
			/* we are dealing with a range of tests, for example:  33 - 44  */
			is_range = 1;
			continue;
		}

		if ( strlen( my_ptr ) > (sizeof( my_buffer ) - 1) ) {
			printf( "-run argument has too many test parameters (max of %lu characters) \n", sizeof( my_buffer ) );
			return -1;
		}
		/* get a local copy of the parameter string to work with - break range into two strings */
		strcpy( &my_buffer[0], my_ptr );

		my_temp_ptr = strrchr( &my_buffer[0], '-' );
		if ( my_temp_ptr != NULL ) {
			/* we are dealing with a range of tests with no white space, for example:  33-44  or  33- 44  */
			my_temp_ptr = strrchr( &my_buffer[0], '-' );
			*my_temp_ptr = 0x00;
			my_first_test_number = strtol( &my_buffer[0], NULL, 10 );
			if ( *(my_temp_ptr + 1) == 0x00 ) {
				/* we hit the case where the range indicator is at the end of one string, for example:  33-  */
				is_range = 1;
				continue;
			}
			my_last_test_number = strtol( (my_temp_ptr + 1), NULL, 10 );
			if ( my_first_test_number < 1 || my_first_test_number > my_last_test_number ) {
				printf( "-run argument has invalid range parmeters \n" );
				return -1;
			}
			mark_tests_to_run( my_first_test_number, my_last_test_number );
			is_range = 0;
			continue;
		}
		
		if ( is_range ) {
			/* should be the second part of the test number range */
			my_last_test_number = strtol( &my_buffer[0], NULL, 10 );
			if ( my_first_test_number < 1 || my_first_test_number > my_last_test_number ) {
				printf( "-run argument has invalid range parmeters \n" );
				return -1;
			}

			mark_tests_to_run( my_first_test_number, my_last_test_number );
			is_range = 0;
			continue;
		}
		else {
			my_first_test_number = strtol( &my_buffer[0], NULL, 10 );
			if ( my_first_test_number < 1 ) {
				printf( "-run argument has invalid test number parameter \n" );
				return -1;
			}
			mark_tests_to_run( my_first_test_number, my_first_test_number );
			continue;
		}
	}
	
	*indexp = i;
	return 0;
	
} /* parse_tests_to_run */


static void create_target_directory( const char * the_targetp )
{
	int			err;
	
	if ( strlen( the_targetp ) > (sizeof(g_target_path) - 1) ) {
		printf( "target path too long - \"%s\" \n", the_targetp );
		exit( 1 );
	}
	
	for ( ;; ) {
        int			my_rand;
        char		my_name[64];
		
        my_rand = rand( );
        sprintf( &my_name[0], "xnu_quick_test-%d", my_rand );
        if ( (strlen( &my_name[0] ) + strlen( the_targetp ) + 2) > PATH_MAX ) {
			printf( "target path plus our test directory name is too long: \n" );
			printf( "target path - \"%s\"  \n", the_targetp );
			printf( "test directory name - \"%s\"  \n", &my_name[0] );
			exit( 1 );
		}

        /* append generated directory name onto our path */
		g_target_path[0] = 0x00;
        strcat( &g_target_path[0], the_targetp );
		if ( g_target_path[ (strlen(the_targetp) - 1) ] != '/' ) {
			strcat( &g_target_path[0], "/" );
		}
        strcat( &g_target_path[0], &my_name[0] );
		
		/* try to create the test directory */
		err = mkdir( &g_target_path[0], (S_IRWXU | S_IRWXG | S_IROTH) );
		if ( err == 0 ) {
			break;
		}
		err = errno;
		if ( EEXIST != err ) {
			printf( "test directory creation failed - \"%s\" \n", &g_target_path[0] );
			printf( "mkdir call failed with error %d - \"%s\" \n", errno, strerror( err) );
			exit( 1 );
		}
	}
	printf( "created test directory at \"%s\" \n", &g_target_path[0] );
	
} /* create_target_directory */


static void mark_tests_to_run( long my_start, long my_end )
{
	int			my_tests_count, i;

	my_tests_count = (sizeof( g_tests ) / sizeof( g_tests[0] ));
	my_end = (my_end < (my_tests_count - 1)) ? my_end : (my_tests_count - 1);
	for ( i = (my_start - 1); i < my_end; i++ ) {
		g_tests[ i ].test_run_it = 1;  /* run this test */
	}
	return;
} /* mark_tests_to_run */


static void usage( void )
{
	char *		my_ptr;
	
	/* skip past full path and just show the tool name */
	my_ptr = strrchr( g_cmd_namep, '/' );
	if ( my_ptr != NULL ) {
		my_ptr++;
	}
	
	printf( "\nUSAGE:  %s -target TARGET_PATH \n\n", (my_ptr != NULL) ? my_ptr : g_cmd_namep );
	printf( "\t -f[ailures] MAX_FAILS_ALLOWED   # number of test cases that may fail before we give up.  defaults to 0  \n" );
	printf( "\t -l[ist]                         # list all the tests this tool performs   \n" );
	printf( "\t -r[un] 1, 3, 10 - 19            # run specific tests.  enter individual test numbers and/or range of numbers.  use -list to list tests.   \n" );
	printf( "\t -s[kip]                         # skip setuid tests   \n" );
	printf( "\t -t[arget] TARGET_PATH           # path to directory where tool will create test files.  defaults to \"/tmp/\"   \n" );
#if !TARGET_OS_EMBEDDED	
	printf( "\t -x[ilog]                        # use XILog\n");
#endif
	printf( "\nexamples:  \n" );
	printf( "--- Place all test files and directories at the root of volume \"test_vol\" --- \n" );
	printf( "%s -t /Volumes/test_vol/ \n", (my_ptr != NULL) ? my_ptr : g_cmd_namep );
	printf( " \n" );
	printf( "--- Run the tool for tests 10 thru 15, test 18 and test 20 --- \n" );
	printf( "%s -r 10-15, 18, 20 \n", (my_ptr != NULL) ? my_ptr : g_cmd_namep );
	printf( " \n" );
	exit( 1 );

} /* usage */

/* This is a private API between Libinfo, Libc, and the DirectoryService daemon.
 * Since we are trying to determine if an external provider will back group
 * lookups, we can use this, without relying on additional APIs or tools
 * that might not work yet */
extern int _ds_running(void);

#define NUM_GROUPS	6
static int
setgroups_if_single_user(void)
{
	int i, retval = -1;
	struct group *grp;
	gid_t gids[NUM_GROUPS];

	if (!_ds_running()) {
		printf("In single-user mode.\n");
		g_is_single_user = 1;		

		/* We skip 'nobody' and 'anyone' */
		getgrent();
		getgrent();
		for (i = 0; i < NUM_GROUPS; i++) {
			grp = getgrent();
			if (!grp) {
				break;
			}

			gids[i] = grp->gr_gid;
		}
		
		endgrent();
		
		/* Only succeed if we find at least NUM_GROUPS */
		if (i == NUM_GROUPS) {
			retval = setgroups(NUM_GROUPS, gids);
			if (retval == 0) { 
				getgroups(NUM_GROUPS, gids);
				printf("After single-user hack, groups are: ");
				for (i = 0; i < NUM_GROUPS; i++) {
					printf("%d, ", gids[i]);
				}
				putchar('\n');
			} else {
				printf("Setgroups failed.\n");
			}
		} else {
			printf("Couldn't get sufficient number of groups.\n");
		}
	} else {
		printf("Not in single user mode.\n");
		retval = 0;
	}


	return retval;
}

static const char *current_arch( void )
{
	cpu_type_t cputype = _mh_execute_header.cputype;
	cpu_subtype_t cpusubtype = _mh_execute_header.cpusubtype;

	const NXArchInfo *arch = NXGetArchInfoFromCpuType(cputype, cpusubtype);

	if (arch) {
		return arch->name;
	} else {
		return "<unknown>";
	}
}

#undef printf	/* this makes the "-l" output easier to read */
static void list_all_tests( void )
{
	int		i, my_tests_count;
	
	my_tests_count = (sizeof( g_tests ) / sizeof( g_tests[0] ));
	printf( "\nList of all tests this tool performs... \n" );

	for ( i = 0; i < (my_tests_count - 1); i++ ) {
		printf( " %d \t   %s \n", (i + 1), g_tests[ i ].test_infop );
	}
	
	return;
} /* list_all_tests */