/*
 * krishna balasubramanian 1993
 *
 * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
 * - added Native Language Support
 *
 * 1999-04-02 frank zago
 * - can now remove several id's in the same call
 *
 */

#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/types.h>
#include "c.h"
#include "nls.h"
#include "strutils.h"
#include "closestream.h"

#ifndef HAVE_UNION_SEMUN
/* according to X/OPEN we have to define it ourselves */
union semun {
	int val;
	struct semid_ds *buf;
	unsigned short int *array;
	struct seminfo *__buf;
};
#endif

typedef enum type_id {
	SHM,
	SEM,
	MSG,
	ALL
} type_id;

static int verbose = 0;

/* print the usage */
static void __attribute__((__noreturn__)) usage(void)
{
	FILE *out = stdout;
	fputs(USAGE_HEADER, out);
	fprintf(out, _(" %1$s [options]\n"
		       " %1$s shm|msg|sem <id>...\n"), program_invocation_short_name);

	fputs(USAGE_SEPARATOR, out);
	fputs(_("Remove certain IPC resources.\n"), out);

	fputs(USAGE_OPTIONS, out);
	fputs(_(" -m, --shmem-id <id>        remove shared memory segment by id\n"), out);
	fputs(_(" -M, --shmem-key <key>      remove shared memory segment by key\n"), out);
	fputs(_(" -q, --queue-id <id>        remove message queue by id\n"), out);
	fputs(_(" -Q, --queue-key <key>      remove message queue by key\n"), out);
	fputs(_(" -s, --semaphore-id <id>    remove semaphore by id\n"), out);
	fputs(_(" -S, --semaphore-key <key>  remove semaphore by key\n"), out);
	fputs(_(" -a, --all[=shm|msg|sem]    remove all (in the specified category)\n"), out);
	fputs(_(" -v, --verbose              explain what is being done\n"), out);

	fputs(USAGE_SEPARATOR, out);
	printf(USAGE_HELP_OPTIONS(28));
	printf(USAGE_MAN_TAIL("ipcrm(1)"));

	exit(EXIT_SUCCESS);
}

static int remove_id(int type, int iskey, int id)
{
        int ret;
	char *errmsg;
	/* needed to delete semaphores */
	union semun arg;
	arg.val = 0;

	/* do the removal */
	switch (type) {
	case SHM:
		if (verbose)
			printf(_("removing shared memory segment id `%d'\n"), id);
		ret = shmctl(id, IPC_RMID, NULL);
		break;
	case MSG:
		if (verbose)
			printf(_("removing message queue id `%d'\n"), id);
		ret = msgctl(id, IPC_RMID, NULL);
		break;
	case SEM:
		if (verbose)
			printf(_("removing semaphore id `%d'\n"), id);
		ret = semctl(id, 0, IPC_RMID, arg);
		break;
	default:
		errx(EXIT_FAILURE, "impossible occurred");
	}

	/* how did the removal go? */
	if (ret < 0) {
		switch (errno) {
		case EACCES:
		case EPERM:
			errmsg = iskey ? _("permission denied for key") : _("permission denied for id");
			break;
		case EINVAL:
			errmsg = iskey ? _("invalid key") : _("invalid id");
			break;
		case EIDRM:
			errmsg = iskey ? _("already removed key") : _("already removed id");
			break;
		default:
			err(EXIT_FAILURE, "%s", iskey ? _("key failed") : _("id failed"));
		}
		warnx("%s (%d)", errmsg, id);
		return 1;
	}
	return 0;
}

static int remove_arg_list(type_id type, int argc, char **argv)
{
	int id;
	char *end = NULL;
	int nb_errors = 0;

	do {
		errno = 0;
		id = strtoul(argv[0], &end, 10);
		if (errno || !end || *end != 0) {
			warnx(_("invalid id: %s"), argv[0]);
			nb_errors++;
		} else {
			if (remove_id(type, 0, id))
				nb_errors++;
		}
		argc--;
		argv++;
	} while (argc);
	return (nb_errors);
}

static int deprecated_main(int argc, char **argv)
{
	type_id type;

	if (!strcmp(argv[1], "shm"))
		type = SHM;
	else if (!strcmp(argv[1], "msg"))
		type = MSG;
	else if (!strcmp(argv[1], "sem"))
		type = SEM;
	else
		return 0;

	if (argc < 3) {
		warnx(_("not enough arguments"));
		errtryhelp(EXIT_FAILURE);
	}

	if (remove_arg_list(type, argc - 2, &argv[2]))
		exit(EXIT_FAILURE);

	printf(_("resource(s) deleted\n"));
	return 1;
}

static unsigned long strtokey(const char *str, const char *errmesg)
{
	unsigned long num;
	char *end = NULL;

	if (str == NULL || *str == '\0')
		goto err;
	errno = 0;
	/* keys are in hex or decimal */
	num = strtoul(str, &end, 0);

	if (errno || str == end || (end && *end))
		goto err;

	return num;
 err:
	if (errno)
		err(EXIT_FAILURE, "%s: '%s'", errmesg, str);
	else
		errx(EXIT_FAILURE, "%s: '%s'", errmesg, str);
	return 0;
}

static int key_to_id(type_id type, char *s)
{
	int id;
	/* keys are in hex or decimal */
	key_t key = strtokey(s, "failed to parse argument");
	if (key == IPC_PRIVATE) {
		warnx(_("illegal key (%s)"), s);
		return -1;
	}
	switch (type) {
	case SHM:
		id = shmget(key, 0, 0);
		break;
	case MSG:
		id = msgget(key, 0);
		break;
	case SEM:
		id = semget(key, 0, 0);
		break;
	case ALL:
		abort();
	default:
		errx(EXIT_FAILURE, "impossible occurred");
	}
	if (id < 0) {
		char *errmsg;
		switch (errno) {
		case EACCES:
			errmsg = _("permission denied for key");
			break;
		case EIDRM:
			errmsg = _("already removed key");
			break;
		case ENOENT:
			errmsg = _("invalid key");
			break;
		default:
			err(EXIT_FAILURE, _("key failed"));
		}
		warnx("%s (%s)", errmsg, s);
	}
	return id;
}

static int remove_all(type_id type)
{
	int ret = 0;
	int id, rm_me, maxid;

	struct shmid_ds shmseg;

	struct semid_ds semary;
	struct seminfo seminfo;
	union semun arg;

	struct msqid_ds msgque;
	struct msginfo msginfo;

	if (type == SHM || type == ALL) {
		maxid = shmctl(0, SHM_INFO, &shmseg);
		if (maxid < 0)
			errx(EXIT_FAILURE,
			     _("kernel not configured for shared memory"));
		for (id = 0; id <= maxid; id++) {
			rm_me = shmctl(id, SHM_STAT, &shmseg);
			if (rm_me < 0)
				continue;
			ret |= remove_id(SHM, 0, rm_me);
		}
	}
	if (type == SEM || type == ALL) {
		arg.array = (ushort *) (void *)&seminfo;
		maxid = semctl(0, 0, SEM_INFO, arg);
		if (maxid < 0)
			errx(EXIT_FAILURE,
			     _("kernel not configured for semaphores"));
		for (id = 0; id <= maxid; id++) {
			arg.buf = (struct semid_ds *)&semary;
			rm_me = semctl(id, 0, SEM_STAT, arg);
			if (rm_me < 0)
				continue;
			ret |= remove_id(SEM, 0, rm_me);
		}
	}
/* kFreeBSD hackery -- ah 20140723 */
#ifndef MSG_STAT
#define MSG_STAT 11
#endif
#ifndef MSG_INFO
#define MSG_INFO 12
#endif
	if (type == MSG || type == ALL) {
		maxid =
		    msgctl(0, MSG_INFO, (struct msqid_ds *)(void *)&msginfo);
		if (maxid < 0)
			errx(EXIT_FAILURE,
			     _("kernel not configured for message queues"));
		for (id = 0; id <= maxid; id++) {
			rm_me = msgctl(id, MSG_STAT, &msgque);
			if (rm_me < 0)
				continue;
			ret |= remove_id(MSG, 0, rm_me);
		}
	}
	return ret;
}

int main(int argc, char **argv)
{
	int c;
	int ret = 0;
	int id = -1;
	int iskey;
	int rm_all = 0;
	type_id what_all = ALL;

	static const struct option longopts[] = {
		{"shmem-id", required_argument, NULL, 'm'},
		{"shmem-key", required_argument, NULL, 'M'},
		{"queue-id", required_argument, NULL, 'q'},
		{"queue-key", required_argument, NULL, 'Q'},
		{"semaphore-id", required_argument, NULL, 's'},
		{"semaphore-key", required_argument, NULL, 'S'},
		{"all", optional_argument, NULL, 'a'},
		{"verbose", no_argument, NULL, 'v'},
		{"version", no_argument, NULL, 'V'},
		{"help", no_argument, NULL, 'h'},
		{NULL, 0, NULL, 0}
	};

	/* if the command is executed without parameters, do nothing */
	if (argc == 1)
		return 0;

	setlocale(LC_ALL, "");
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
	close_stdout_atexit();

	/* check to see if the command is being invoked in the old way if so
	 * then remove argument list */
	if (deprecated_main(argc, argv))
		return EXIT_SUCCESS;

	/* process new syntax to conform with SYSV ipcrm */
	while((c = getopt_long(argc, argv, "q:m:s:Q:M:S:a::vhV", longopts, NULL)) != -1) {
		iskey = 0;
		switch (c) {
		case 'M':
			iskey = 1;
			id = key_to_id(SHM, optarg);
			if (id < 0) {
				ret++;
				break;
			}
			/* fallthrough */
		case 'm':
			if (!iskey)
				id = strtos32_or_err(optarg, _("failed to parse argument"));
			if (remove_id(SHM, iskey, id))
				ret++;
			break;
		case 'Q':
			iskey = 1;
			id = key_to_id(MSG, optarg);
			if (id < 0) {
				ret++;
				break;
			}
			/* fallthrough */
		case 'q':
			if (!iskey)
				id = strtos32_or_err(optarg, _("failed to parse argument"));
			if (remove_id(MSG, iskey, id))
				ret++;
			break;
		case 'S':
			iskey = 1;
			id = key_to_id(SEM, optarg);
			if (id < 0) {
				ret++;
				break;
			}
			/* fallthrough */
		case 's':
			if (!iskey)
				id = strtos32_or_err(optarg, _("failed to parse argument"));
			if (remove_id(SEM, iskey, id))
				ret++;
			break;
		case 'a':
			rm_all = 1;
			if (optarg) {
				if (!strcmp(optarg, "shm"))
					what_all = SHM;
				else if (!strcmp(optarg, "msg"))
					what_all = MSG;
				else if (!strcmp(optarg, "sem"))
					what_all = SEM;
				else
					errx(EXIT_FAILURE,
					     _("unknown argument: %s"), optarg);
			} else {
				what_all = ALL;
			}
			break;
		case 'v':
			verbose = 1;
			break;

		case 'h':
			usage();
		case 'V':
			print_version(EXIT_SUCCESS);
		default:
			errtryhelp(EXIT_FAILURE);
		}
	}

	if (rm_all && remove_all(what_all))
		ret++;

	/* print usage if we still have some arguments left over */
	if (optind < argc) {
		warnx(_("unknown argument: %s"), argv[optind]);
		errtryhelp(EXIT_FAILURE);
	}

	return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
