[comp.sources.misc] Semaphore facilities exerciser for System V

eric@snark.UUCP (Eric S. Raymond) (08/17/87)

[I'm testing the new version of my submitter; send me mail if this is screwed
up somehow.  ++bsa]

/*****************************************************************************

NAME
    semex -- interactive exerciser for System V semaphore operations

SYNOPSIS
    semex

DESCRIPTION
    This is an interactive exerciser for the semctl(2), semget(2) and
semop(2) system calls of System V UNIX. You can use it to experiment with
the semaphore features in order to understand them better. It includes
on-line help.

    Calls that might cause semex to block (semop(2) with negative operation
values) are handed to a forked copy of semex. Semex's children emit reports
just before they block and when they unblock, to enable the user to track
what's going on.

    Semex can also be used as a semaphores interface for scripts. Command
prompting is suppressed if stdin isn't a tty; so are the verbose
descriptions of actions performed that it normally emits, and the
process-forking described above.

COMMANDS
    All arguments of every command are optional; the code tries to give
reasonable defaults to any you leave off. Anytime a `semid' or `semnum'
argument is given, the default semid or semnum for future commands is set
to it.

    c(reate) key nsems -[ce] perms -- create a semaphore group

    This calls semget(2). The `key' argument should be a long, the `nsems'
argument an int. The third (flags) argument of the semget(2) call is
created from the third and fourth arguments of this command; flags
`c' and `e' stand for IPC_CREAT and IPC_EXCL respectively and `perms'
should be at least three digits of octal permission mask.
    The `current semaphore group id' (semid) is set to the return value
of this command. 
    The default arguments are `0L 1 - 0660' (note that 0L = IPC_PRIVATE).

    f(ind) semid                   -- select a semaphore group by id

    This changes semex's notion of the current semaphore group id. If the
argument is omitted, the current value is simply printed out.

    i(ndex) semnum                 -- select a semaphore index

    This changes semex's notion of the current semaphore index. If the
argument is omitted, the current value is simply printed out.

    d(o) op -[un]                  -- do a semaphore operation

    This command does a semop(2) call. The first argument of the call will
be the currently selected semaphore id. The third argument (number of
operations) will be 1. The (struct sembuf *) second argument will point
to a single operation structure.
    The sem_num field of this structure is set to the `current semaphore
index' value set by the `n' command (normally 1). The sem_op field is set
to the value of the `op' command argument (which should be an integer). The
sem_flg field is set according to the flags in the third command argument;
`u' stands for SEM_UNDO, `n' for IPC_NOWAIT.
    The argument defaults are `0 -'.

    v(alue) semid semnum            -- query a semaphore's semval

    This command displays the return of a semctl(semid, semnum, GETVAL). If
semnum is omitted it defaults to the currently selected semaphore index. If
semid is omitted it defaults to the currently selected semaphore id.

    p(id) semid semnum             -- query a semaphore's sempid

    This command displays the return of a semctl(semid, semnum, GETPID). If
semnum is omitted it defaults to the currently selected semaphore index. If
semid is omitted it defaults to the currently selected semaphore id.

    n(cnt) semid semnum            -- query a semaphore's semncnt

    This command displays the return of a semctl(semid, semnum, GETNCNT). If
semnum is omitted it defaults to the currently selected semaphore index. If
semid is omitted it defaults to the currently selected semaphore id.

    z(cnt) semid semnum            -- query a semaphore's semzcnt

    This command displays the return of a semctl(semid, semnum, GETZCNT). If
semnum is omitted it defaults to the currently selected semaphore index. If
semid is omitted it defaults to the currently selected semaphore id.

    s(et) semval		   -- set the value of a semaphore

    This command does a semop(2) call using the SETVAL command to set
the value of the currently selected semaphore. The first argument of the call
will be the currently selected semaphore id. The second argument of the call
will be the currently selected semaphore index. The fourth (value) argument
will be the semval argument of the command (which defaults to 0 if omitted).

    m(mask) semid uid gid mode    -- query/set a semaphore's mode

    The m command with no arguments displays the uid, gid and mode
of the currently selected semaphore group. With one argument, it displays
this information for the given semaphore group. With two or more arguments
it sets whatever portions of the mode and ownership data are given; uid and
gid should be decimal integers and mode at least three digits of octal.

    r(emove) semid                -- remove a semaphore group

    Do a semctl(2) to remove a semaphore group. If the semid argument is
omitted, the currently selected semaphore group will be removed.

    l(ist)                        -- run ipcs -sbopt

    This is just a convenience. It displays data on active semaphores.

    x(it)                         -- exit

    Exit semex. Child semex processes created by 'd' commands will get SIGHUP
and die gracefully.

    ! cmd                         -- execute a shell command

    Escape to a shell.    

    ?                      -- print this help message

In addition, typing a newline will simply display the value of the
currently selected semaphore (newline is a synonym for the `v' command).

NOTE
    The AT&T documentation entries for semop(2) tell lies about the type of
the second argument. System V Release 1 claims the type is

    struct sembuf (*)[]

and its lint library entry is correspondingly broken. Release 2 claims
that it's

    struct sembuf **

but its lint library checks for the correct type, which is

    struct sembuf *

as one can actually deduce from the description text for both entries.

BUGS
   The 'p' and 'v' commands are not Dyskstra's P and V operations.
   There is no way for semex to specify more than one semaphore op at once
in semop(2).
   There is no support for exercising the GETALL or SETALL modes of semctl(2).

AUTHOR
   Eric S. Raymond {{ihnp4,seismo}!cbmvax,hplabs!sdcrdcf!burdvax}!snark!eric

*****************************************************************************/
/* LINTLIBRARY */

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

extern int errno;
extern void exit();
extern char *strchr(), *strcat();
extern unsigned sleep();

/*
 * This will need to change if any future version changes the define values
 * of any UNIX error types. The highest value currently recognized is
 * EIDRM; the code that uses it in errmsg() will issue a generic error type
 * for higher values.
 */
static char *errtypes[] =
{
    "?",
    "EPERM",		/* Not super-user			*/
    "ENOENT",		/* No such file or directory		*/
    "ESRCH",		/* No such process			*/
    "EINTR",		/* interrupted system call		*/
    "EIO",		/* I/O error				*/
    "ENXIO",		/* No such device or address		*/
    "E2BIG",		/* Arg list too long			*/
    "ENOEXEC",		/* Exec format error			*/
    "EBADF",		/* Bad file number			*/
    "ECHILD",		/* No children				*/
    "EAGAIN",		/* No more processes			*/
    "ENOMEM",		/* Not enough core			*/
    "EACCES",		/* Permission denied			*/
    "EFAULT",		/* Bad address				*/
    "ENOTBLK",		/* Block device required		*/
    "EBUSY",		/* Mount device busy			*/
    "EEXIST",		/* File exists				*/
    "EXDEV",		/* Cross-device link			*/
    "ENODEV",		/* No such device			*/
    "ENOTDIR",		/* Not a directory			*/
    "EISDIR",		/* Is a directory			*/
    "EINVAL",		/* Invalid argument			*/
    "ENFILE",		/* File table overflow			*/
    "EMFILE",		/* Too many open files			*/
    "ENOTTY",		/* Not a typewriter			*/
    "ETXTBSY",		/* Text file busy			*/
    "EFBIG",		/* File too large			*/
    "ENOSPC",		/* No space left on device		*/
    "ESPIPE",		/* Illegal seek				*/
    "EROFS",		/* Read only file system		*/
    "EMLINK",		/* Too many links			*/
    "EPIPE",		/* Broken pipe				*/
    "EDOM",		/* Math arg out of domain of func	*/
    "ERANGE",		/* Math result not representable	*/
    "ENOMSG",		/* No message of desired type		*/
    "EIDRM",		/* Identifier removed			*/
};

static ushort	parent;
static int	tty;

static void sigabort(sig)
/* log the occurrence of a signal, die gracefully if it's SIGHUP */
int	sig;	/* the signal number */
{
    (void) fprintf("Received signal %d\n", sig);
    if (sig == SIGHUP)
	exit(0);
}

static char *errmsg(code)
/* return the UNIX error message for a given errno code */
int code;
{
    extern int sys_nerr;
    extern char *sys_errlist[];
    static char ebuf[BUFSIZ];

    (void) sprintf(ebuf, "error %d", code);

    if (code < sizeof(errtypes) / sizeof(char *))
    {
	(void) strcat(ebuf, " (");
	(void) strcat(ebuf, errtypes[code]);
	(void) strcat(ebuf, ")");
    }

    if (code <= sys_nerr)
    {
	(void) strcat(ebuf, ", ");
	(void) strcat(ebuf, sys_errlist[code]);
    }

    return(ebuf);
}

void shsemop(semid, sops, nops)
/* perform a semaphore op, announcing the specifics of the operation */
int		semid;
struct sembuf	*sops;
int		nops;
{
    if (tty)
	(void) fprintf(stdout,
		"semop(%d, {%hd, %hd, %hd}, %d) in process %d\n",
		semid, sops->sem_num, sops->sem_op, sops->sem_flg, 
	    nops, getpid());

    nops = semop(semid, sops, nops);

    if (tty)
    {
	/* if we're in the parent, give child processes time to report */
	if (getpid() == parent)
	    (void) sleep(2);

	if (nops == -1)
	    (void) fprintf(stdout,"semop in process %d failed: %s\n",
			    getpid(), errmsg(errno));
	else
	    (void) fprintf(stdout,
			    "semop in process %d succeeded, returning %d\n",
			    getpid(), nops);
    }
}

main(argc, argv)
/* excercise the semaphore functions */
int	argc;
char	*argv[];
{
    key_t	    key = IPC_PRIVATE;
    int		    sc, rv, nsems, perms, semflg, semid, semnum = 0;
    struct sembuf   sop;
    char	    cmdline[BUFSIZ], strbuf[BUFSIZ];

    parent = getpid();
    tty = isatty(fileno(stdin));

    for (rv = SIGHUP; rv <= SIGTERM; rv++)
	(void) signal(rv, sigabort);

    if (tty)
    {
	(void) puts("This is the semaphore exerciser, type ? for help");
	(void) fprintf(stdout, "The exerciser process pid is %d\n", parent);
    }
    while ((!tty || fputs("> ", stdout) != EOF) && gets(cmdline))
    {
	errno = 0;

	switch(cmdline[0])
	{
	case 'c':	/* create and select a new semaphore */
	    key = IPC_PRIVATE;
	    nsems = 1;
	    perms = 0660;	/* read & alter by owning user & group */
	    semflg = 0;

	    sc = sscanf(cmdline, "c %ld %d %s %o", &key,&nsems,strbuf,&perms);

	    if (sc >= 3)
	    {
		if (strchr(strbuf, 'c') != (char *)NULL)
		    semflg |= IPC_CREAT;

		if (strchr(strbuf, 'e') != (char *)NULL)
		    semflg |= IPC_EXCL;
	    }
	    semflg |= perms;

	    semid = semget(key, nsems, semflg);
	    if (tty)
	    {
		(void) printf("semget(%ld, %d, %04o) ",	key, nsems, semflg);
		if (semid == -1)
		    (void) printf("failed: %s\n", errmsg(errno));
		else
		    (void) printf("succeeded, semid = %d\n", semid);
	    }
	    break;

	case 'f':	/* select a given semaphore group */
	    (void) sscanf("f %d", &semid);
	    (void) printf("Semaphore id %d selected\n", semid);
	    break;

	case 'i':	/* set current semaphore number */
	    (void) sscanf("i %d", &semnum);
	    (void) printf("Semaphore index %d selected\n", semnum);
	    break;

	case 'd':	/* perform a semaphore operation */
	    sc = sscanf(cmdline, "d %hd %s", &sop.sem_op, strbuf);
	    sop.sem_num = semnum;

	    if (sc == 0)
		sop.sem_op = 0;

	    if (sc <= 1)
		sop.sem_flg = 0;

	    if (sc == 2)
	    {
		if (strchr(strbuf, 'u') != (char *)NULL)
		    sop.sem_flg |= SEM_UNDO;

		if (strchr(strbuf, 'n') != (char *)NULL)
		    sop.sem_flg |= IPC_NOWAIT;
	    }

	    if (tty && sop.sem_op < 0)
	    {
		if (rv = fork())	/* parent side */
		{
		    (void)fprintf(stdout,"Spawning child with pid = %d\n",rv);
		    (void) sleep(1);
		}		    
		else			/* child side */
		{
		    (void) sleep(1);
		    shsemop(semid, &sop, 1);
		    exit(0);
		}
	    }
	    else
		shsemop(semid, &sop, 1);
	    break;

	case 'v':
	case '\0':
	    (void) sscanf(cmdline, "v %d %d\n", &semid, &semnum);
	    if (tty)
		(void) printf("semctl(%d, %d, GETVAL) returns %d\n",
			semid, semnum, semctl(semid, semnum, GETVAL));
	    break;

	case 'p':
	    (void) sscanf(cmdline, "p %d %d\n", &semid, &semnum);
	    if (tty)
		(void) printf("semctl(%d, %d, GETPID) returns %d\n",
			semid, semnum, semctl(semid, semnum, GETPID));
	    break;

	case 'n':
	    (void) sscanf(cmdline, "n %d %d\n", &semid, &semnum);
	    if (tty)
		(void) printf("semctl(%d, %d, GETNCNT) returns %d\n",
			semid, semnum, semctl(semid, semnum, GETNCNT));
	    break;

	case 'z':
	    (void) sscanf(cmdline, "z %d %d\n", &semid, &semnum);
	    if (tty)
		(void) printf("semctl(%d, %d, GETZCNT) returns %d\n",
			semid, semnum, semctl(semid, semnum, GETZCNT));
	    break;

	case 's':	/* set the value of a semaphore */
	    {
		int	semval = 0;

		(void) sscanf(cmdline, "s %d", &semval);

		rv = semctl(semid, semnum, SETVAL, semval);
		if (tty)
		{
		    (void) printf("semctl(%d, %d, SETVAL, %d) ",
					    semid, semnum, semval);
		    if (rv == -1)
			(void) printf("failed: %s\n", errmsg(errno));
		    else
			(void) printf("succeeded, returning %d\n", rv);
		}
	    }
	    break;

	case 'm':	/* query-set mode information */
	    {
		int			uid, gid, mode;
		struct semid_ds		ds;

		sc = sscanf(cmdline, "m %d %d %d %o", &semid,&uid,&gid,&mode);

		rv = semctl(semid, semnum, IPC_STAT, &ds);

		/* retrieve and show the existing modes */
		(void) printf("semctl(%d, %d, IPC_STAT, &ds) ", semnum,semid);
		if (rv == -1)
		    (void) printf("failed: %s\n", errmsg(errno));
		else
		{
		(void) printf("succeeded, returning %d\n", rv);
		(void) printf("Here is the semaphore group data:\nds = {\n");
		(void) printf("    sem_perm.uid = %d\n", ds.sem_perm.uid);
		(void) printf("    sem_perm.gid = %d\n", ds.sem_perm.gid);
		(void) printf("    sem_perm.mode = %04o\n", ds.sem_perm.mode);
		(void) printf("    sem_nsems = %d\n", ds.sem_nsems);
		(void) printf("    sem_otime = (%ld) %s",
					ds.sem_otime, ctime(&ds.sem_otime));
		(void) printf("    sem_ctime = (%ld) %s}\n",
					ds.sem_ctime, ctime(&ds.sem_ctime));
		}

		/* if two or more arguments were given, set new modes */
		if (sc >= 2)
		{
		    ds.sem_perm.uid = uid;
		    if (sc >= 3)
			ds.sem_perm.gid = gid;
		    if (sc >= 4)
			ds.sem_perm.mode = mode;

		    rv = semctl(semid, semnum, IPC_SET, &ds);
		    if (tty)
		    {
			(void) printf("semctl(%d, %d, IPC_SET, &ds) ",
					semnum, semid);
			if (rv == -1)
			    (void) printf("failed: %s\n", errmsg(errno));
			else
			    (void) printf("succeeded, returning %d\n", rv);
		    }
		}
	    }
	    break;

	case 'r':	/* delete an existing semaphore */
	    (void) sscanf(cmdline, "r %d", &semid);

	    rv = semctl(semid, 0, IPC_RMID);

	    if (tty)
	    {
		(void) printf("semctl(%d, 0, IPC_RMID) ", semid);
		if (rv == -1)
		    (void) printf("failed: %s\n", errmsg(errno));
		else
		    (void) printf("succeeded\n");
	    }
	    break;

	case 'l':	/* list status of semaphore groups */
	    (void) system("exec ipcs -sbopt");
	    break;

	case 'x':	/* leave */
	    return;

	case '!':	/* escape to a shell */
	    (void) system(cmdline + 1);
	    break;

	case '?':	/* print on-line help */
printf("c(reate) key nsems [ce] perms  -- create a semaphore group\n");
printf("f(ind) semid                   -- select a semaphore group by id\n");
printf("i(ndex) semnum                 -- select a semaphore index\n");
printf("d(o) op [un]                   -- do a semaphore operation\n");
printf("v(alue) semid semnum           -- query a semaphore's semval\n");
printf("p(id) semid semnum             -- query a semaphore's sempid\n");
printf("n(cnt) semid semnum            -- query a semaphore's semncnt\n");
printf("z(cnt) semid semnum            -- query a semaphore's semzcnt\n");
printf("m(mask) semid uid gid mode     -- query/set a semaphore's mode\n");
printf("r(emove) semid                 -- remove a semaphore group\n");
printf("s(et) semval                   -- set the value of a semaphore\n");
printf("l(ist)                         -- run ipcs -sbopt\n");
printf("x(it)                          -- exit\n\n");

printf("! cmd                          -- execute a shell command\n");
printf("?                              -- print this help message\n\n");
	    break;

	default:
	    (void)printf("Illegal command -- type ? for help\n");
	    break;
	}

	(void) sleep(1);
    }
}

/* semex.c ends here */
-- 
      Eric S. Raymond
      UUCP:  {{seismo,ihnp4,rutgers}!cbmvax,sdcrdcf!burdvax}!snark!eric
      Post:  22 South Warren Avenue, Malvern, PA 19355
      Phone: (215)-296-ed w