/*
 * Try to exercise each of the error returns in the message passing code.
 *
 * This is an extreme case of white box testing.  This program may get
 * out of date due to changes in the implmentation but it should always
 * be a valid test program since all the conditions that it tests are
 * 'defined'.
 *
 * The only message that you get should be "errno test passed".
 *
 * This program will fail if run by root.
 */

#include <stdio.h>

#include <sys/types.h>
#define KERNEL
#include <sys/ipc.h>
#include <sys/msg.h>
#undef KERNEL
#include <errno.h>
#include <signal.h>

#define LOTS_OF_ENTRIES		10000

int errcnt = 0;
const char *doing_str = "";

void
failure(const char *msg)
{
    fprintf(stderr,"%s\n",msg);
    errcnt += 1;
}

void
verify( int got, int expected_rval, int expected_errno, const char *msg )
{
    char tbuf[1000];

    if ( got == expected_rval ) {
	if ( got != -1 ) {
	    return;
	}
	if ( errno == expected_errno ) {
	    return;
	}
	sprintf(tbuf,"got errno=%d, expected errno=%d (%s:%s)",errno,expected_errno,doing_str,msg);
    } else if ( got == -1 ) {
	sprintf(tbuf,"got errno=%d, expected %d (%s:%s)",errno,expected_rval,doing_str,msg);
    } else {
	sprintf(tbuf,"got %d, expected %d (%s:%s)",got,expected_rval,doing_str,msg);
    }
    failure(tbuf);
}

void
doing(const char *str)
{
    doing_str = str;
}

void
catch_alarm(int code)
{
}

main()
{
    int msqid, bad_msqid;
    int vector[LOTS_OF_ENTRIES];
    struct msqid_ds ds, orig_ds;
    int i;
    struct { long typ; char data[100]; } msg;
    struct ipc_perm perm;

    errcnt = 0;

    /*
     * Delete any existing queue.
     */

    msgctl( msgget(0x33445566, 0), IPC_RMID, NULL );

    /*
     * Allocate a queue to play with
     */

    msqid = msgget(0x33445566, IPC_CREAT |  0700);
    if ( msqid < 0 ) {
	perror("msgget failed");
	exit(1);
    }

    /*
     * Get a queue id that we know is invalid
     */

    msgctl( bad_msqid = msgget(0x33447777, IPC_CREAT | IPC_EXCL), IPC_RMID, NULL );
    if ( bad_msqid < 0 ) {
	perror("msgget #2 failed");
	exit(1);
    }

    /*
     * Error codes in msgctl
     */

    doing("msgctl");
    verify( msgctl(-1, IPC_STAT, NULL), -1, EINVAL, "msqid out of range" );
    perm.seq = 0;
    verify( msgctl( IXSEQ_TO_IPCID(IPCID_TO_IX(bad_msqid),perm), IPC_STAT, NULL), -1, EINVAL, "no such msqid" );
    verify( msgctl( IXSEQ_TO_IPCID(IPCID_TO_IX(msqid),perm), IPC_STAT, NULL), -1, EINVAL, "bad sequence number" );

    /* cmd == IPC_RMID */

    /* can't check removal permissions this way ... */

    /* cmd == IPC_SET */

    verify( msgctl(msqid,IPC_STAT, NULL), -1, EFAULT, "NULL IPC_STAT addr" );
    verify( msgctl(msqid,IPC_SET, NULL), -1, EFAULT, "NULL IPC_SET addr" );

    verify( msgctl(msqid,IPC_STAT, &orig_ds), 0, 0, "get the current ds entry" );
    ds = orig_ds;
    ds.msg_qbytes += 1;
    verify( msgctl(msqid, IPC_SET, &ds), -1, EPERM, "attempt to increase qbytes" );
    ds.msg_qbytes = 0;
    verify( msgctl(msqid, IPC_SET, &ds), -1, EINVAL, "attempt to set qbytes to 0" );
    ds = orig_ds;
    verify( msgctl(msqid, IPC_SET, &ds), 0, 0, "attempt to set to current values" );
    verify( msgctl(msqid, IPC_STAT, &ds), 0, 0, "get 'unchanged' ds entry'" );
    ds.msg_ctime = orig_ds.msg_ctime;	/* This one changes legitimately */

    if ( ds.msg_perm.cuid != orig_ds.msg_perm.cuid ) {
	failure("ds msg_perm cuid changed");
    }
    if ( ds.msg_perm.cgid != orig_ds.msg_perm.cgid ) {
	failure("ds msg_perm cgid changed");
    }
    if ( ds.msg_perm.uid != orig_ds.msg_perm.uid ) {
	failure("ds msg_perm uid changed");
    }
    if ( ds.msg_perm.gid != orig_ds.msg_perm.gid ) {
	failure("ds msg_perm gid changed");
    }
    if ( ds.msg_perm.mode != orig_ds.msg_perm.mode ) {
	failure("ds msg_perm mode changed");
    }
    if ( ds.msg_perm.seq != orig_ds.msg_perm.seq ) {
	failure("ds msg_perm seq changed");
    }
    if ( ds.msg_perm.key != orig_ds.msg_perm.key ) {
	failure("ds msg_perm key changed");
    }
    if ( ds.msg_first != orig_ds.msg_first ) {
	failure("ds msg_first changed");
    }
    if ( ds.msg_last != orig_ds.msg_last ) {
	failure("ds msg_last changed");
    }
    if ( ds.msg_cbytes != orig_ds.msg_cbytes ) {
	failure("ds msg_cbytes changed");
    }
    if ( ds.msg_lspid != orig_ds.msg_lspid ) {
	failure("ds msg_lspid changed");
    }
    if ( ds.msg_lrpid != orig_ds.msg_lrpid ) {
	failure("ds msg_lrpid changed");
    }
    if ( ds.msg_stime != orig_ds.msg_stime ) {
	failure("ds msg_stime changed");
    }
    if ( ds.msg_ctime != orig_ds.msg_ctime ) {
	failure("ds msg_ctime changed");
    }

    ds.msg_perm.mode = 0;
    verify( msgctl(msqid, IPC_SET, &ds), 0, 0, "set mode bits to 0" );
    verify( msgctl(msqid, IPC_SET, &ds), 0, 0, "set mode bits to 0 #2" );
    verify( msgctl(msqid, IPC_STAT, &ds), -1, EACCES, "IPC_STAT with no permission" );
    verify( msgctl(msqid, IPC_SET, &orig_ds), 0, 0, "set mode bits to 0" );

    /*
     * Error codes in msgget
     */

    doing("msgget");
    verify( msgget(0x33445566, IPC_EXCL | IPC_CREAT), -1, EEXIST, "exclusive creation" );
    verify( msgget(0x33447777, 0), -1, ENOENT, "get non-existent queue" );
    for ( i = 0; i < LOTS_OF_ENTRIES; i += 1 ) {
	vector[i] = msgget(IPC_PRIVATE, 0);
	if ( vector[i] < 0 ) {
	    verify( vector[i], -1, ENOSPC, "no more msqid_ds's available" );
	    break;
	}
    }
    if ( i == LOTS_OF_ENTRIES ) {
	failure("created tooooo many msqid_ds's");
    }
    i -= 1;
    while ( i >= 0 ) {
	verify( msgctl(vector[i],IPC_RMID,NULL),0,0,"remove vector entry");
	i -= 1;
    }

    /*
     * Error codes in msgsnd
     */

    doing("msgsnd");
    verify( msgsnd(-1,NULL,10,IPC_NOWAIT), -1, EINVAL, "msqid out of range" );
    perm.seq = 0;
    verify( msgsnd(IXSEQ_TO_IPCID(IPCID_TO_IX(bad_msqid),perm),NULL,10,IPC_NOWAIT), -1, EINVAL, "no such msqid" );
    verify( msgsnd(IXSEQ_TO_IPCID(IPCID_TO_IX(msqid),perm),NULL,10,IPC_NOWAIT), -1, EINVAL, "bad sequence number" );
    ds = orig_ds;
    ds.msg_perm.mode &= ~0200;	/* Use constants to validate #define values as well */
    msg.typ = 2;
    verify( msgctl(msqid, IPC_SET, &ds), 0, 0, "remove write permission" );
    verify( msgsnd(msqid, &msg, 10, IPC_NOWAIT), -1, EACCES, "send with no write permission" );
    verify( msgctl(msqid, IPC_SET, &orig_ds), 0, 0, "restore write permission" );
    verify( msgsnd(msqid, &msg, 10, IPC_NOWAIT), 0, 0, "send with permission" );
    verify( msgsnd(msqid, NULL, 10, IPC_NOWAIT), -1, EFAULT, "NULL msg pointer" );
    msg.typ = 0;
    verify( msgsnd(msqid, &msg, 10, IPC_NOWAIT), -1, EINVAL, "invalid message type" );
    msg.typ = 2;
    verify( msgsnd(msqid, &msg, orig_ds.msg_qbytes+1, IPC_NOWAIT), -1, EINVAL, "message too long #1" );
    verify( msgsnd(msqid, &msg, orig_ds.msg_qbytes+1, 0), -1, EINVAL, "message too long #2" );	/* IPC_NOWAIT shouldn't matter */

    /*
     * Fill the queue up with tiny messages.
     *
     * Note that it isn't possible to predict which resource we'll run
     * out of.  It really doesn't matter since all we want is to be able
     * to block waiting for more resources of some sort.
     */

    while (1) {
	int rval = msgsnd(msqid, &msg, 2, IPC_NOWAIT);
	if ( rval != 0 ) {
	    break;
	}
    }

    if ( errno == EAGAIN ) {

	signal(SIGALRM,catch_alarm);
	alarm(1);	/* interrupt ourselves soon */
	verify( msgsnd(msqid, &msg, 2, 0), -1, EINTR, "alarm signal" );	/* blocks until EINTR happens */

	alarm(3);	/* make sure we get out of this somehow */
	if ( fork() ) {
	    verify( msgsnd(msqid, &msg, 2, IPC_NOWAIT), -1, EAGAIN, "queue still there" );
	    verify( msgsnd(msqid, &msg, 2, 0), -1, EINVAL, "queue gone" );	/* blocks until RMID below happens */
	} else {
	    sleep(1);
	    msgctl(msqid, IPC_RMID, NULL);	/* nuke the queue */
	    exit(0);
	}

	alarm(0);
	signal(SIGALRM,SIG_DFL);

    } else {

	verify( -1, -1, EAGAIN, "fill queue with tiny messages" );

    }

    /*
     * Start fresh
     */

    msgctl( msqid, IPC_RMID, NULL );
    msqid = msgget(0x33445566, IPC_CREAT |  0700);
    if ( msqid < 0 ) {
	perror("msgget #3 failed");
	exit(1);
    }
    msgctl( bad_msqid = msgget(0x33447777, IPC_CREAT | IPC_EXCL), IPC_RMID, NULL );
    if ( bad_msqid < 0 ) {
	perror("msgget #4 failed");
	exit(1);
    }

    /*
     * Queue up some messages for use below
     */

    verify( msgsnd(msqid, &msg, 10, IPC_NOWAIT), 0, 0, "send with permission (get ready for msgrcv testing)" );	/* consumed by #1 below */
    verify( msgsnd(msqid, &msg, 10, IPC_NOWAIT), 0, 0, "send with permission (get ready for msgrcv testing)" );	/* consumed by #2 below */
    verify( msgsnd(msqid, &msg, 10, IPC_NOWAIT), 0, 0, "send with permission (get ready for msgrcv testing)" );	/* consumed by #3 below */

    /*
     * Error codes in msgrcv
     */

    doing("msgrcv");
    verify( msgrcv(-1,NULL,10,0,IPC_NOWAIT), -1, EINVAL, "msqid out of range" );
    perm.seq = 0;
    verify( msgrcv(IXSEQ_TO_IPCID(IPCID_TO_IX(bad_msqid),perm),NULL,10,0,IPC_NOWAIT), -1, EINVAL, "no such msqid" );
    verify( msgrcv(IXSEQ_TO_IPCID(IPCID_TO_IX(msqid),perm),NULL,10,0,IPC_NOWAIT), -1, EINVAL, "bad sequence number" );
    ds = orig_ds;
    ds.msg_perm.mode &= ~0400;	/* Use constants to validate #define values as well */
    msg.typ = 1;
    verify( msgctl(msqid, IPC_SET, &ds), 0, 0, "remove write permission" );
    verify( msgrcv(msqid,&msg,10,0,IPC_NOWAIT), -1, EACCES, "receive without read permission" );
    verify( msgctl(msqid, IPC_SET, &orig_ds), 0, 0, "restore read permission" );
    verify( msgrcv(msqid, &msg, 10, 1, IPC_NOWAIT), -1, EAGAIN, "receive wrong type #1" );
    verify( msgrcv(msqid, &msg, 10, -1, IPC_NOWAIT), -1, EAGAIN, "receive wrong type #2" );
    verify( msgrcv(msqid, &msg, 9, 2, IPC_NOWAIT), -1, E2BIG, "receive too short (typ=2)" );
    verify( msgrcv(msqid, &msg, 9, 0, IPC_NOWAIT), -1, E2BIG, "receive too short (typ=0)" );
    verify( msgrcv(msqid, &msg, 9, 2, MSG_NOERROR | IPC_NOWAIT), 9, 0, "trunc receive too short (typ=2)" );	/* consumes a message #1 */
    verify( msgrcv(msqid, &msg, 9, 0, MSG_NOERROR | IPC_NOWAIT), 9, 0, "trunc receive too short (typ=0)" );	/* consumes a message #2 */
    verify( msgrcv(msqid, NULL, 10, 2, IPC_NOWAIT), -1, EFAULT, "NULL msg pointer" );	/* consumes a message #2 */
    verify( msgrcv(msqid, &msg, 10, 2, IPC_NOWAIT), -1, EAGAIN, "no more messages" );

    signal(SIGALRM,catch_alarm);
    alarm(1);	/* interrupt ourselves soon */
    verify( msgrcv(msqid, &msg, 2, 0, 0), -1, EINTR, "alarm signal" );

    /*
     * Remove the queue while waiting for a message
     */

    alarm(3);	/* make sure we get out of this somehow */
    if ( fork() ) {
	verify( msgrcv(msqid, &msg, 2, 0, IPC_NOWAIT), -1, EAGAIN, "queue still here" );
	verify( msgrcv(msqid, &msg, 2, 0, 0), -1, EINVAL, "queue gone" );
    } else {
	sleep(1);
	msgctl(msqid, IPC_RMID, NULL);	/* nuke the queue */
	exit(0);
    }
    alarm(0);
    signal(SIGALRM,SIG_DFL);

    /*
     * Done.  How did we do?
     */

    if ( errcnt == 0 ) {
	fprintf(stderr,"errno test passed\n");
	exit(0);
    } else {
	fprintf(stderr,"%d error%s in errno test\n",errcnt,errcnt==0 ? "" : "s");
	exit(1);
    }
}
