/******************************************************************************/
/*                                                                            */
/* Copyright (c) 2008 FUJITSU LIMITED                                         */
/*                                                                            */
/* This program is free software;  you can redistribute it and/or modify      */
/* it under the terms of the GNU General Public License as published by       */
/* the Free Software Foundation; either version 2 of the License, or          */
/* (at your option) any later version.                                        */
/*                                                                            */
/* This program is distributed in the hope that it will be useful,            */
/* but WITHOUT ANY WARRANTY;  without even the implied warranty of            */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See                  */
/* the GNU General Public License for more details.                           */
/*                                                                            */
/* You should have received a copy of the GNU General Public License          */
/* along with this program;  if not, write to the Free Software               */
/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA    */
/*                                                                            */
/* Author: Li Zefan <lizf@cn.fujitsu.com>                                     */
/*                                                                            */
/******************************************************************************/

#include <sys/socket.h>
#include <sys/poll.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <linux/types.h>
#include <linux/netlink.h>

#ifndef NETLINK_CONNECTOR

int main(void)
{
	return 2;
}

#else

#include <linux/connector.h>

#ifndef CN_IDX_PROC

int main(void)
{
	return 2;
}

#else

#define _LINUX_TIME_H
#include <linux/cn_proc.h>

#define PEC_MSG_SIZE (sizeof(struct cn_msg) + sizeof(struct proc_event))
#define PEC_CTRL_MSG_SIZE (sizeof(struct cn_msg) + sizeof(enum proc_cn_mcast_op))

#define MAX_MSG_SIZE 256

static __u32 seq;

static int exit_flag;
static struct sigaction sigint_action;

struct nlmsghdr *nlhdr;

/*
 * Handler for signal int. Set exit flag.
 *
 * @signo: the signal number, not used
 */
static void sigint_handler(int __attribute__ ((unused)) signo)
{
	exit_flag = 1;
}

/*
 * Send netlink package.
 *
 * @sd: socket descripor
 * @to: the destination sockaddr
 * @cnmsg: the pec control message
 */
static int netlink_send(int sd, struct sockaddr_nl *to, struct cn_msg *cnmsg)
{
	int ret;
	struct iovec iov;
	struct msghdr msg;

	memset(nlhdr, 0, NLMSG_SPACE(MAX_MSG_SIZE));

	nlhdr->nlmsg_seq = seq++;
	nlhdr->nlmsg_pid = getpid();
	nlhdr->nlmsg_type = NLMSG_DONE;
	nlhdr->nlmsg_len = NLMSG_LENGTH(sizeof(*cnmsg) + cnmsg->len);
	nlhdr->nlmsg_flags = 0;
	memcpy(NLMSG_DATA(nlhdr), cnmsg, sizeof(*cnmsg) + cnmsg->len);

	memset(&iov, 0, sizeof(struct iovec));
	iov.iov_base = (void *)nlhdr;
	iov.iov_len = nlhdr->nlmsg_len;

	memset(&msg, 0, sizeof(struct msghdr));
	msg.msg_name = (void *)to;
	msg.msg_namelen = sizeof(*to);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;

	ret = sendmsg(sd, &msg, 0);

	return ret;
}

/*
 * Receive package from netlink.
 *
 * @sd: socket descripor
 * @from: source sockaddr
 */
static int netlink_recv(int sd, struct sockaddr_nl *from)
{
	int ret;
	struct iovec iov;
	struct msghdr msg;

	memset(nlhdr, 0, NLMSG_SPACE(MAX_MSG_SIZE));
	memset(&iov, 0, sizeof(iov));
	memset(&msg, 0, sizeof(msg));

	iov.iov_base = (void *)nlhdr;
	iov.iov_len = NLMSG_SPACE(MAX_MSG_SIZE);

	msg.msg_name = (void *)from;
	msg.msg_namelen = sizeof(*from);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;

	ret = recvmsg(sd, &msg, 0);

	return ret;
}

/*
 * Send control message to PEC.
 *
 * @sd: socket descriptor
 * @to: the destination sockaddr
 * @op: control flag
 */
static int control_pec(int sd, struct sockaddr_nl *to, enum proc_cn_mcast_op op)
{
	int ret;
	char buf[PEC_CTRL_MSG_SIZE];
	struct cn_msg *cnmsg;
	enum proc_cn_mcast_op *pec_op;

	memset(buf, 0, sizeof(buf));

	cnmsg = (struct cn_msg *)buf;
	cnmsg->id.idx = CN_IDX_PROC;
	cnmsg->id.val = CN_VAL_PROC;
	cnmsg->seq = seq++;
	cnmsg->ack = 0;
	cnmsg->len = sizeof(op);

	pec_op = (enum proc_cn_mcast_op *)cnmsg->data;
	*pec_op = op;

	ret = netlink_send(sd, to, cnmsg);

	return ret;
}

/*
 * Process PEC event.
 *
 * @nlhdr: the netlinke pacakge
 */
static void process_event(struct nlmsghdr *nlhdr)
{
	struct cn_msg *msg;
	struct proc_event *pe;

	msg = (struct cn_msg *)NLMSG_DATA(nlhdr);

	pe = (struct proc_event *)msg->data;

	switch (pe->what) {
	case PROC_EVENT_NONE:
		printf("none err: %u\n", pe->event_data.ack.err);
		break;
	case PROC_EVENT_FORK:
		printf("fork parent: %d, child: %d\n",
		       pe->event_data.fork.parent_pid,
		       pe->event_data.fork.child_pid);
		break;
	case PROC_EVENT_EXEC:
		printf("exec pid: %d\n", pe->event_data.exec.process_pid);
		break;
	case PROC_EVENT_UID:
		printf("uid pid: %d euid: %d ruid: %d\n",
		       pe->event_data.id.process_pid,
		       pe->event_data.id.e.euid, pe->event_data.id.r.ruid);
		break;
	case PROC_EVENT_GID:
		printf("gid pid: %d egid: %d rgid: %d\n",
		       pe->event_data.id.process_pid,
		       pe->event_data.id.e.egid, pe->event_data.id.r.rgid);
		break;
	case PROC_EVENT_EXIT:
		printf("exit pid: %d exit_code: %d exit_signal: %d\n",
		       pe->event_data.exit.process_pid,
		       pe->event_data.exit.exit_code,
		       pe->event_data.exit.exit_signal);
		break;
	default:
		printf("unknown event\n");
		break;
	}
}

int main(int argc, char **argv)
{
	int ret;
	int sd;
	struct sockaddr_nl l_local;
	struct sockaddr_nl src_addr;
	struct pollfd pfd;

	sigint_action.sa_flags = SA_RESETHAND;
	sigint_action.sa_handler = &sigint_handler;
	sigaction(SIGINT, &sigint_action, NULL);

	nlhdr = malloc(NLMSG_SPACE(MAX_MSG_SIZE));
	if (!nlhdr) {
		fprintf(stderr, "lack of memory\n");
		exit(1);
	}

	sd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
	if (sd == -1) {
		fprintf(stderr, "failed to create socket\n");
		exit(1);
	}

	memset(&src_addr, 0, sizeof(src_addr));
	src_addr.nl_family = AF_NETLINK;
	src_addr.nl_pid = 0;
	src_addr.nl_groups = 0;

	memset(&l_local, 0, sizeof(l_local));
	l_local.nl_family = AF_NETLINK;
	l_local.nl_pid = getpid();
	l_local.nl_groups = CN_IDX_PROC;

	ret = bind(sd, (struct sockaddr *)&l_local, sizeof(struct sockaddr_nl));
	if (ret == -1) {
		fprintf(stderr, "failed to bind socket\n");
		exit(1);
	}

	/* Open PEC listening */
	ret = control_pec(sd, &src_addr, PROC_CN_MCAST_LISTEN);
	if (!ret) {
		fprintf(stderr, "failed to open PEC listening\n");
		exit(1);
	}

	/* Receive msg from PEC */
	pfd.fd = sd;
	pfd.events = POLLIN;
	pfd.revents = 0;
	while (!exit_flag) {

		ret = poll(&pfd, 1, -1);
		if (ret == 0 || (ret == -1 && errno != EINTR)) {
			control_pec(sd, &src_addr, PROC_CN_MCAST_IGNORE);
			fprintf(stderr, "failed to poll\n");
			exit(1);
		} else if (ret == -1 && errno == EINTR)
			break;

		ret = netlink_recv(sd, &src_addr);

		if (ret == 0)
			break;
		else if (ret == -1 && errno == EINTR)
			break;
		else if (ret == -1 && errno != EINTR) {
			control_pec(sd, &src_addr, PROC_CN_MCAST_IGNORE);
			fprintf(stderr, "failed to receive from netlink\n");
			exit(1);
		} else {
			switch (nlhdr->nlmsg_type) {
			case NLMSG_ERROR:
				fprintf(stderr, "err message recieved.\n");
				exit(1);
				break;
			case NLMSG_DONE:
				/* message sent from kernel */
				if (nlhdr->nlmsg_pid == 0)
					process_event(nlhdr);
				break;
			default:
				break;
			}
		}
	}

	/* Close PEC listening */
	ret = control_pec(sd, &src_addr, PROC_CN_MCAST_IGNORE);
	if (!ret) {
		fprintf(stderr, "failed to close PEC listening\n");
		exit(1);
	}

	close(sd);
	free(nlhdr);

	while (fsync(STDOUT_FILENO) == -1) {
		if (errno != EIO)
			break;
		/* retry once every 10 secodns */
		sleep(10);
	}

	return 0;
}

#endif /* CN_IDX_PROC */

#endif /* NETLINK_CONNECTOR */
