#include <fcntl.h>
#include <limits.h>
#include <pwd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h> 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "pt1_ioctl.h"

#ifdef O_NOFOLLOW
#define OPEN_FLAGS O_NOFOLLOW | O_NONBLOCK
#else
#define OPEN_FLAGS O_NONBLOCK
#endif

/* ヘッダに移動すること */
#define C_RET_OK 0
#define C_RET_NG -1
#define MYNAME "pt1_lnb_enabler"
#define PID_DIR "/var/run/"
#define PIDFILENAME PID_DIR MYNAME ".pid"
#define NUMBER "0123456789"
#define device "/dev/pt1video0"
#define ROOT "root"
/* ヘッダに移動すること */

mode_t umask_val = 0133;

void fin_action(int);

int main() {
	struct sigaction ign_sigaction;
	struct sigaction fin_sigaction;
	FILE *fp;
	int i;
	int i_ret;
	int fd;
	int pt1_fd;
	int string_length;
	int number_length;
	int i_pid;
	struct stat orig_st;
	struct stat open_st;
	int flags;
	char file_name[] = PIDFILENAME;
	char buf[1024] = "";
	pid_t mypid;
	char *p;
        struct passwd *p_st_passwd;
        uid_t uid_root;

	/* 初期処理 */
        /* root権限で動作していなければ終了 */
        p_st_passwd = getpwnam(ROOT);
        if (p_st_passwd == NULL) {
            printf("faile to get pwent. id=[%s].\n", ROOT);
            exit(C_RET_NG);
        }
        uid_root = p_st_passwd->pw_uid;
        if( uid_root != getuid() ) {
            /* root 意外の実行権限なのでエラー */
            printf("This process must be run by root. uid=[%d].\n", getuid());
            exit(C_RET_NG);
        }
        /* fork して親は即自殺してターミナルを離す */
	mypid = fork();
	if( mypid == -1 ) {
		/* fork エラー */
		printf("fork error.\n");
		exit(C_RET_NG);
	} else if ( mypid != 0 ) {
		/* 親プロセスは即終了(プロンプトに戻す) */
		return(C_RET_OK);
	}
	umask(umask_val);
	ign_sigaction.sa_handler = SIG_IGN;
	fin_sigaction.sa_handler = fin_action;
	if (sigaction(SIGHUP, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGHUP\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGTERM, &fin_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGTERM\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGPIPE, &fin_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGPIPE\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGINT, &fin_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGINT\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGQUIT, &fin_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGQUIT\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGILL, &fin_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGILL\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGABRT, &fin_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGABRT\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGFPE, &fin_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGFPE\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGSEGV, &fin_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGSEGV\n");
		exit(C_RET_NG);
	}
        /* sleep を使うので SIGALRM は放置
	* if (sigaction(SIGALRM, &ign_sigaction, NULL) != 0) {
	* 	printf("failed to set signal handler. SIGALRM\n");
	* 	exit(C_RET_NG);
	* }
        */
	if (sigaction(SIGUSR1, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGUSR1\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGUSR2, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGUSR2\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGCHLD, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGCHLD\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGCONT, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGCONT\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGTSTP, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGTSTP\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGTTIN, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGTTIN\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGTTOU, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGTTOU\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGPOLL, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGPOLL\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGIO, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGIO\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGPROF, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGPROF\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGSYS, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGSYS\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGTRAP, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGTRAP\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGURG, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGURG\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGVTALRM, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGVTALRM\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGXCPU, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGXCPU\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGXFSZ, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGXFSZ\n");
		exit(C_RET_NG);
	}
	/* ないって
	 * if (sigaction(SIGEMT, &ign_sigaction, NULL) != 0) {
	 *	 printf("failed to set signal handler. SIGEMT\n");
	 *	 exit(C_RET_NG);
	 * }
	 */
	if (sigaction(SIGSTKFLT, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGSTKFLT\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGIO, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGIO\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGCLD, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGCLD\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGPWR, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGPWR\n");
		exit(C_RET_NG);
	}
	/* ないって
	 * if (sigaction(SIGINFO, &ign_sigaction, NULL) != 0) {
	 *	printf("failed to set signal handler. SIGINFO\n");
	 *	exit(C_RET_NG);
	 * }
	 */
	/* ないって
	 * if (sigaction(SIGLOST, &ign_sigaction, NULL) != 0) {
	 *	printf("failed to set signal handler. SIGLOST\n");
	 *	exit(C_RET_NG);
	 * }
	 */
	if (sigaction(SIGWINCH, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGWINCH\n");
		exit(C_RET_NG);
	}
	if (sigaction(SIGUNUSED, &ign_sigaction, NULL) != 0) {
		printf("failed to set signal handler. SIGUNUSED\n");
		exit(C_RET_NG);
	}

	/* ファイルチェック処理 */
	if ((lstat(file_name, &orig_st) != 0) ||
			(!S_ISREG(orig_st.st_mode)))
	{
		/* ファイル無しの場合は前回正常終了 */
		i_ret = C_RET_OK;
	} else {
		/* PIDファイルが存在する場合には、現在の状態を確認する。
		 * ・二重起動状態(pidファイルのpid値を持つプロセスが存在する)
		 * ・前回pidファイルを消さずに死んでしまった */
		i_ret = C_RET_NG;
	}
	/* ここに TOCTOU 競合状態の問題があるが、
	 * /var/run はセキュアなディレクトリなので問題は無い */
	if (i_ret != C_RET_OK) {
		fd = open(file_name, (OPEN_FLAGS | O_RDWR));
		if (fd == -1) {
			/* エラー処理 */
			printf("open error. file:[%s].\n", file_name);
			exit(C_RET_NG);
		}

		if (fstat(fd, &open_st) != 0) {
			/* エラー処理 */
			printf("stat error. file:[%s].\n", file_name);
			exit(C_RET_NG);
		}

		if ((orig_st.st_mode != open_st.st_mode) ||
				(orig_st.st_ino  != open_st.st_ino) ||
				(orig_st.st_dev  != open_st.st_dev)) {
			/* ファイルはすでにすり替えられている */
			printf("file switch has occurred. file:[%s].\n", file_name);
			close(fd);
			exit(C_RET_NG);
		}

		/* 問題ないファイルであることが確認できたので O_NONBLOCK
		 * を無効にする (省略可能) */
		if ((flags = fcntl(fd, F_GETFL)) == -1) {
			/* エラー処理 */
			printf("fcntl error. file:[%s].\n", file_name);
			close(fd);
			exit(C_RET_NG);
		}

		if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) != 0) {
			/* エラー処理 */
			printf("fcntl error. file:[%s].\n", file_name);
			close(fd);
			exit(C_RET_NG);
		}
		/* ファイルサイズが0以外である場合にはPID値のチェック */
		if(open_st.st_size != 0) {
			/* ファイルを読みPID値を取得する */
			if((fp = fdopen(fd, "w+")) == NULL) {
				/* fdopen()失敗 */
				printf("fdopen error. fd:[%d].\n", fd);
				close(fd);
				exit(C_RET_NG);
			}
			if ( fgets(buf, sizeof(buf), fp) == NULL ) {
				/* fgetsエラー */
				printf("fgets error. fd:[%d].\n", fd);
				fclose(fp);
				exit(C_RET_NG);
			}
			buf[sizeof(buf)-1] = '\0';
			p = strchr(buf, (int)'\n');
			if(p != NULL) {
				*p = '\0';
			}
			string_length = strlen(buf);
			number_length = strspn(buf, NUMBER);
			if ( string_length != number_length ) {
				/* PIDファイル異常 */
				printf("invalid pid file[%s] buf[%s].\n", file_name, buf);
				fclose(fp);
				exit(C_RET_NG);
			}
			i_pid = atoi(buf);

			/* PIDファイルのPIDと同じPIDを持つプロセスが存在するか？ */
			i_ret = kill(i_pid, 0);
			if ( i_ret == 0 ) {
				/* 存在する場合には二重起動とみなして処理終了 */
				printf("process already exists. pid[%d]\n", i_pid);
				exit(C_RET_NG);
			} else {
				/* 存在しない場合は前回異常終了なのでfpを先頭に戻して処理続行 */
				rewind(fp);
			}
		}
	}
	if ( i_ret == C_RET_OK ) {
		/* PIDファイルが存在しない場合はPIDファイルの新規作成を行う */
		fd = open(file_name, O_CREAT|O_EXCL|O_RDWR, 00644);
		if ( fd == -1 ){
			/* open 失敗 */
			printf("file open error. file_name[%s].\n", file_name);
			exit(C_RET_NG);
		}
		if((fp = fdopen(fd, "w+")) == NULL) {
			/* fdopen()失敗 */
			printf("fdopen error. fd:[%d].\n", fd);
			close(fd);
			exit(C_RET_NG);
		}
	}
	/* fpのPIDファイルが書き込める状態なので、PIDを書き込む */
	mypid = getpid();
	snprintf(buf, sizeof(buf), "%d\n", mypid);
	i_ret = fputs(buf, fp);
	if(i_ret == EOF) {
		/* fputs エラー */
		printf("fputs error. file_name[%s].\n", file_name);
		fclose(fp);
		exit(C_RET_NG);
	}
	fclose(fp);
	/* PID関係処理終了*/
	/* fd に pt1 デバイスを open */
	pt1_fd = open(device, O_RDONLY);
	if(pt1_fd == -1) {
		/* open エラー */
		printf("open error. file_name[%s].\n", device);
		exit(C_RET_NG);
	}
	/* fcntl で LNB 電源を有効化する */
	if(ioctl(pt1_fd, LNB_ENABLE, 0) < 0) {
		printf("Power on LNB failed. device:[%s].\n", device);
		exit(C_RET_NG);
	}
	close(pt1_fd);
	/* 無限待ち(終了は終了するsignalを受けたらとする) */
	while(1){
		sleep(UINT_MAX);
	}
	return C_RET_OK;
}

/* 終了処理実行関数 */
/* プロセスを終了させるべきsignalを受けたときに動作する */
void fin_action(int sig) {
	int pt1_fd;
	char file_name[] = PIDFILENAME;
	/* fd に pt1 デバイスをopen */
	pt1_fd = open(device, O_RDONLY);
	if(pt1_fd == -1) {
		/* open エラー */
		printf("open error. file_name[%s].\n", device);
		exit(C_RET_NG);
	}
	/* fcntl で LNB 電源断する */
	if(ioctl(pt1_fd, LNB_DISABLE, 0) < 0) {
		printf("Power on LNB failed. device:[%s].\n", device);
		close(pt1_fd);
		exit(C_RET_NG);
	}
	/* fd close */
	close(pt1_fd);
	/* pidファイルを削除する */
	if ( unlink(file_name) != 0 ) {
		/* pid file unlink エラー */
		printf("unlink error. file_name[%s].\n", device);
		exit(C_RET_NG);
	}
	/* おしまい */
	exit(C_RET_OK);
}
