/*****************************************************************************
 Excerpt from "Linux Programmer's Guide - Chapter 6"
 (C)opyright 1994-1995, Scott Burkett
 ***************************************************************************** 
 MODULE: semtool.c
 *****************************************************************************
 A command line tool for tinkering with SysV style Semaphore Sets

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

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

#define SEM_RESOURCE_MAX	1

void opensem(int *sid, key_t key);
void createsem(int *sid, key_t key, int members);
void locksem(int sid, int member);
void unlocksem(int sid, int member);
void removesem(int sid);
unsigned short get_member_count(int sid);
int getval(int sid, int member);
void dispval(int sid, int member);
void changemode(int sid, char *mode);
void usage(void);

int main(int argc, char *argv[])
{
	key_t key;
	int   semset_id;

	if(argc == 1)
		usage();

	/* Create unique key via call to ftok() */
	key = ftok(".", 's');

	switch(tolower(argv[1][0]))
	{
		case 'c': if(argc != 3)
				usage();
			  createsem(&semset_id, key,  atoi(argv[2]));
			  break;
		case 'l': if(argc != 3)
				usage();
			  opensem(&semset_id, key);
			  locksem(semset_id, atoi(argv[2]));
			  break;
		case 'u': if(argc != 3)
				usage();
			  opensem(&semset_id, key);
			  unlocksem(semset_id, atoi(argv[2]));
			  break;
		case 'd': opensem(&semset_id, key);
			  removesem(semset_id);
			  break;	
		case 'm': opensem(&semset_id, key);
			  changemode(semset_id, argv[2]);
			  break;	
		 default: usage();

	}
	
	exit(0);

/*NOTREACHED*/
}

void opensem(int *sid, key_t key)
{
	/* Open the semaphore set - do not create! */

	if((*sid = semget(key, 0, 0666)) == -1) 
	{
		printf("Semaphore set does not exist!\n");
		exit(1);
	}

}

void createsem(int *sid, key_t key, int members)
{
	int cntr;
	union semun semopts;

	if(members > SEMMSL) {
		printf("Sorry, max number of semaphores in a set is %d\n",
			SEMMSL);
		exit(1);
	}

	printf("Attempting to create new semaphore set with %d members\n",
				members);

	if((*sid = semget(key, members, IPC_CREAT|IPC_EXCL|0666))
			== -1) 
	{
		fprintf(stderr, "Semaphore set already exists!\n");
		exit(1);
	}

	semopts.val = SEM_RESOURCE_MAX;
	
	/* Initialize all members (could be done with SETALL) */	
	for(cntr=0; cntr<members; cntr++)
		semctl(*sid, cntr, SETVAL, semopts);
}

void locksem(int sid, int member)
{
	struct sembuf sem_lock={ 0, -1, IPC_NOWAIT};

	if( member<0 || member>(get_member_count(sid)-1))
	{
		fprintf(stderr, "semaphore member %d out of range\n", member);
		return;
	}

 	/* Attempt to lock the semaphore set */
	if(!getval(sid, member))
	{
		fprintf(stderr, "Semaphore resources exhausted (no lock)!\n");
		exit(1);
	}
	
	sem_lock.sem_num = member;
	
	if((semop(sid, &sem_lock, 1)) == -1)
	{
		fprintf(stderr, "Lock failed\n");
		exit(1);
	}
	else
		printf("Semaphore resources decremented by one (locked)\n");

	dispval(sid, member);
}

void unlocksem(int sid, int member)
{
	struct sembuf sem_unlock={ member, 1, IPC_NOWAIT};
	int semval;

	if( member<0 || member>(get_member_count(sid)-1))
	{
		fprintf(stderr, "semaphore member %d out of range\n", member);
		return;
	}

	/* Is the semaphore set locked? */
	semval = getval(sid, member);
	if(semval == SEM_RESOURCE_MAX) {
		fprintf(stderr, "Semaphore not locked!\n");
		exit(1);
	}

	sem_unlock.sem_num = member;

 	/* Attempt to lock the semaphore set */
	if((semop(sid, &sem_unlock, 1)) == -1)
	{
		fprintf(stderr, "Unlock failed\n");
		exit(1);
	}
	else
		printf("Semaphore resources incremented by one (unlocked)\n");

	dispval(sid, member);
}

void removesem(int sid)
{
	semctl(sid, 0, IPC_RMID, 0);
	printf("Semaphore set removed\n");
}

unsigned short get_member_count(int sid)
{
	union semun semopts;
	struct semid_ds mysemds;

	semopts.buf = &mysemds;

	/* Return number of members in the semaphore set */
	return(semopts.buf->sem_nsems);
}

int getval(int sid, int member)
{
	int semval;

	semval = semctl(sid, member, GETVAL, 0);
	return(semval);
}

void changemode(int sid, char *mode)
{
	int rc;
	union semun semopts;
	struct semid_ds mysemds;

	/* Get current values for internal data structure */
	semopts.buf = &mysemds;

	rc = semctl(sid, 0, IPC_STAT, semopts);

	if (rc == -1) {
		perror("semctl");
		exit(1);
	}
		
	printf("Old permissions were %o\n", semopts.buf->sem_perm.mode);
	 	
	/* Change the permissions on the semaphore */
	sscanf(mode, "%ho", &semopts.buf->sem_perm.mode);

	/* Update the internal data structure */
	semctl(sid, 0, IPC_SET, semopts);

	printf("Updated...\n");

}

void dispval(int sid, int member)
{
	int semval;

	semval = semctl(sid, member, GETVAL, 0);
	printf("semval for member %d is %d\n", member, semval);
}

void usage(void)
{
	fprintf(stderr, "semtool - A utility for tinkering with semaphores\n");
	fprintf(stderr, "\nUSAGE:  semtool4 (c)reate <semcount>\n");
	fprintf(stderr, "                 (l)ock <sem #>\n");
	fprintf(stderr, "                 (u)nlock <sem #>\n");
	fprintf(stderr, "                 (d)elete\n");
	fprintf(stderr, "                 (m)ode <mode>\n");
	exit(1);
}

