/*
 *	mqueue.c
 *
 *	POSIX Message Queue for Windows NT
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2016 Naoyuki Sawa
 *
 *	* Fri Mar 18 21:34:32 JST 2016 Naoyuki Sawa
 *	- 1st リリース。
 *	  pthreads-win32にはPOSIX Message Queueが含まれていないので、追加する事にしました。
 *	  このモジュールはWin32専用です。P/ECEでは使用出来ません。	←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←注意
 *	- 以下の実装を参考にさせて頂き、より簡略化した実装にしました。
 *	  □参考資料
 *	  │「POSIX Message Queues for Win32!」(https://sourceware.org/ml/pthreads-win32/2000/msg00131.html)
 *	  主に簡略化した点は、以下の通りです。
 *	  ・オリジナル版の実装は、プロセス間でメッセージの送受信が行えるように、共有メモリを使って実装されていました。
 *	    当実装は、プロセス内のスレッド間でメッセージの送受信が行えれば充分なので、普通のメモリ確保を使用しました。
 *	  ・当実装はプロセス間通信をサポートしないので、メッセージキューの名前は無意味になり、当実装はmq_open()のname引数,及び,mode引数を無視します。
 *	    それに伴って、mq_unlink()も、何もしないダミー関数としました。
 *	  ・オリジナル版の実装は、本来の関数仕様通りに、エラーが発生した時は、エラーコードを返すようになっていました。
 *	    当実装は、エラーが発生した時に、'DIE()'で停止するようにしました。
 *	  ・mq_notify()は未対応であり、呼び出すとエラー停止します。
 *	    mq_notify()が未対応である事は、オリジナル版の実装も同じです。
 *	- 関数仕様については、以下の資料を参照しました。
 *	  □参照資料
 *	  │「Linux Programmer's Manual - MQ_OVERVIEW」(https://linuxjm.osdn.jp/html/LDP_man-pages/man7/mq_overview.7.html)
 *	  │及び、上記のページからリンクされている、各関数の説明のページ
 *	* Sat Mar 19 21:19:21 JST 2016 Naoyuki Sawa
 *	- /clip/include/sys/types.h(又は/clip/clipstdw.h)にssize_tの定義を追加した事に伴い、これまでssize_tの代りにintを使っていた部分を正式なssize_tに修正しました。
 *	  2016/03/19時点でint⇒ssize_tに修正した箇所は、getline(),getdelim(),mq_receive()です。
 *	* Thu Sep 08 22:20:43 JST 2016 Naoyuki Sawa
 *	- (unsigned)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
 *	  詳細は、msg_hdr_compar(),mq_send()内のコメントを参照して下さい。
 *	- mq_send()が、(int)でアドレス演算を行っていたのを、(char*)で行うように修正しました。
 *	  mq_receive()が、(int)でアドレス演算を行っていたのを、(char*)で行うように修正しました。
 *	  64ビット対応のための準備ですが、32ビットでも(char*)で行う方が本当は正しいからです。
 *	  とは言え、32ビットではどちらでも同じ結果になるので、既存の32ビットコードには影響有りません。
 */
#include "clip.h"
/****************************************************************************
 *	
 ****************************************************************************/
//メッセージヘッダ
struct msg_hdr {						//one msg_hdr{} at the front of each message in the mapped file
	unsigned		msg_prio;			//priority
	size_t			msg_len;			//actual length
	char			msg_data[0/*mq_msgsize*/];	//copy of the message
};
/*--------------------------------------------------------------------------*/
//メッセージキューヘッダ
struct mq_hdr {							//one mq_hdr{} per queue, at beginning of mapped file
	struct mq_attr		mqh_attr;			//the queue's attributes
	pthread_mutex_t		mqh_lock;			//mutex lock
	pthread_cond_t		mqh_wait;			//and condition variable
	struct msg_hdr*		mqh_mhdr[0/*mq_maxmsg*/];	//message headers
};
/****************************************************************************
 *	
 ****************************************************************************/
mqd_t mq_open(const char* name, int oflag, .../*mode_t mode, struct mq_attr* attr*/) {
	va_list ap;
	mode_t mode;			//未使用
	struct mq_attr* attr;
	struct mq_hdr* mqhdr;
	struct msg_hdr* mhdr;
	int i;
	//当実装はプロセス間通信をサポートしないので、mq_open()には常に新しいメッセージキューを作成させるはずであり、oflag引数にO_CREATを指定せずに当関数を呼び出す事は出来ません。
	//O_CREATは必須、O_NONBLOCKは任意、O_RDONLY,O_WRONLY,O_RDWR,O_CLOEXEC,O_EXCLは無視します。
	if(!(oflag & O_CREAT)) { DIE(); }
	//oflagにO_CREATを指定する場合、追加で2つの引き数を与える必要がある。
	//mode引き数は、新しいキューに適用される許可設定(permission)を、open()と同じように指定する。	←当実装では無視します。
	//attr引き数は、キューの属性を指定する。							←当実装でも参照します。
	va_start(ap, oflag);
	mode = va_arg(ap, mode_t);	//未使用
	attr = va_arg(ap, struct mq_attr*);
	va_end(ap);
	//attrがNULLの場合、キューは実装で定義されたデフォルト属性で作成される。
	if(!attr) {
		static const struct mq_attr defattr = {
			0,	//mq_flags
			10,	//mq_maxmsg	┬一般的なLinuxシステムのカーネルパラメータでは(10,8192)みたいなのでそれに準拠する事にしました。
			8192,	//mq_msgsize	┘
			0};	//mq_curmsgs
		attr = (struct mq_attr*)&defattr;
	}
	//oflagにO_CREATが指定され、かつattrがNULL以外だが、attr->mq_maxmsgかattr->mq_msqsizeが不正であった。
	//これらのフィールドは両方とも0より大きくなければならない。
	if((attr->mq_maxmsg  <= 0) ||
	   (attr->mq_msgsize <= 0)) { DIE(); }	//EINVAL
	//メッセージキューヘッダと、メッセージヘッダへのポインタ配列と、メッセージヘッダと、メッセージデータバッファを、まとめてメモリ確保する。
	mqhdr = malloc(sizeof(struct mq_hdr  )                                        +	//mq_hdr						メッセージキューヘッダ
	              (sizeof(struct msg_hdr*)                     * attr->mq_maxmsg) +	//mq_hdr.mqh_mhdr[mq_maxmsg]				メッセージヘッダへのポインタ配列
	              (sizeof(struct msg_hdr ) + attr->mq_msgsize) * attr->mq_maxmsg);	//msg_hdr + (msg_hdr.msg_data[mq_msgsize] * mq_maxmsg)	メッセージヘッダとメッセージデータバッファ
	if(!mqhdr) { DIE(); }	//ENOMEM
	//メッセージキューヘッダを初期化する。
	mqhdr->mqh_attr.mq_flags	= oflag & O_NONBLOCK;
	mqhdr->mqh_attr.mq_maxmsg	= attr->mq_maxmsg;
	mqhdr->mqh_attr.mq_msgsize	= (attr->mq_msgsize + 3) & ~3;	//size of message in file is rounded up for alignment
	mqhdr->mqh_attr.mq_curmsgs	= 0;
	//ミューテクスと、条件変数を作成する。
	if(pthread_mutex_init(&mqhdr->mqh_lock, NULL)) { DIE(); }
	if(pthread_cond_init( &mqhdr->mqh_wait, NULL)) { DIE(); }
	//メッセージヘッダへのポインタ配列を初期化する。
	mhdr = (struct msg_hdr*)&mqhdr->mqh_mhdr[attr->mq_maxmsg];	//メッセージヘッダへのポインタ配列の直後に、メッセージヘッダとメッセージデータバッファのメモリが確保されている。
	for(i = 0; i < attr->mq_maxmsg; i++) {
		mqhdr->mqh_mhdr[i] = mhdr;
		mhdr = (struct msg_hdr*)&mhdr->msg_data[attr->mq_msgsize];	//ポインタを、次のメッセージヘッダとメッセージデータバッファのアドレスへ進める。
	}
	//成功すると、mq_open()はメッセージキュー記述子(message queue descriptor)を返す。
	//メッセージキュー記述子は他のメッセージキュー関連の関数で使用される。
	//エラーの場合、mq_open()は(mqd_t)-1を返し、errnoにエラーを示す値を設定する。	←当実装ではエラーの場合プログラムを終了するので、当関数がエラーを返す事は無い。
	return mqhdr;
}
/*--------------------------------------------------------------------------*/
int mq_close(mqd_t mqdes) {
	struct mq_hdr* const mqhdr = mqdes;
	//ミューテクスと、条件変数を削除する。
	if(pthread_cond_destroy( &mqhdr->mqh_wait)) { DIE(); }
	if(pthread_mutex_destroy(&mqhdr->mqh_lock)) { DIE(); }
	//mq_open()にてまとめて確保したメモリを、まとめて開放する。
	free(mqhdr);
	//成功すると、mq_close()は0を返す。
	//エラーの場合、-1を返し、errnoにエラーを示す値を設定する。	←当実装ではエラーの場合プログラムを終了するので、当関数がエラーを返す事は無い。
	return 0;
}
/*--------------------------------------------------------------------------*/
int mq_unlink(const char* name) {
	//当実装はプロセス間通信をサポートしないので、メッセージキューの名前は無意味になり、当実装はmq_open()のname引数,及び,mode引数を無視します。
	//それに伴って、mq_unlink()も、何もしないダミー関数としました。
	/** no job **/
	return 0;
}
/*--------------------------------------------------------------------------*/
static int msg_hdr_compar(const void* key, const void* datum) {
//{{2016/09/08変更:(unsigned)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
//	unsigned const msg_prio = (unsigned)key;
//↓2016/09/08変更:(unsigned)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
#ifndef _WIN64
	unsigned const msg_prio = (unsigned)key;	//32ビット版は、これまでと変更有りません。32ビット版も64ビット版と同じ書き方にしても動作は変わらないのですが、簡潔さを優先してこれまでのままとする事にしました。
#else //_WIN64
	unsigned const msg_prio = (uintptr_t)key;	//64ビット版は、(void*)(64ビット)⇒(unsigned)(32ビット)にキャストしようとすると警告が出ます。元の値が32ビットの整数パラメータなので(unsigned)にキャストしても安全ではあるのですが、警告を回避するために(void*)(64ビット)⇒(uintptr_t)(64ビット)⇒暗黙の(unsigned)(32ビット)へのキャスト、という手順を取る事にしました。
#endif//_WIN64
//}}2016/09/08変更:(unsigned)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
	struct msg_hdr* const mhdr = *(struct msg_hdr**)datum;
	if(msg_prio > mhdr->msg_prio) { return -1; }	//┬msg_prio引き数は、メッセージの優先度を指定する負でない整数である。メッセージは優先度の降順でキューに格納され、同じ優先度の新しいメッセージは同じ優先度の古いメッセージの後ろに格納される。
	if(msg_prio < mhdr->msg_prio) { return  1; }	//┘                                                                                      ~~~~
	return 0;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int mq_send(mqd_t mqdes, const char* msg_ptr, size_t msg_len, unsigned msg_prio) {
	struct mq_hdr* const mqhdr = mqdes;
	struct mq_attr* const attr = &mqhdr->mqh_attr;
	struct msg_hdr *mhdr, **insert_pos, **remove_pos;
	//メッセージキューをロックする。
	if(pthread_mutex_lock(&mqhdr->mqh_lock)) { DIE(); }
	//sg_len引き数は、msg_ptrが指すメッセージの長さを示す。
	//この長さはキューのmq_msgsize属性以下でなければならない。
	//長さが0のメッセージも認められている。
	if((unsigned)msg_len > (unsigned)attr->mq_msgsize) { DIE(); }	//EMSGSIZE
	//メッセージキューがすでに一杯の場合(すなわち、キューに入っているメッセージ数がキューのmq_maxmsg属性と等しい場合)、デフォルトでは、mq_send()は、メッセージをキューイングするのに十分な空間ができるか、関数呼び出しがシグナルハンドラーにより中断されるまで、停止(block)する。	←当実装では、シグナルハンドラーにより中断される事は無い。
	//メッセージキュー記述(message queue description)でO_NONBLOCKフラグが有効になっている場合は、mq_send()はエラーEAGAINですぐに失敗する。
	while(attr->mq_curmsgs >= attr->mq_maxmsg) {
		if(attr->mq_flags & O_NONBLOCK) {
			//メッセージキューのロックを解除する。
			if(pthread_mutex_unlock(&mqhdr->mqh_lock)) { DIE(); }	//忘れないで!!
			errno = EAGAIN;
			return -1;
		}
		//キューに入っているメッセージ数が変化するのを待つ。
		if(pthread_cond_wait(&mqhdr->mqh_wait, &mqhdr->mqh_lock)) { DIE(); }
	}
	//メッセージヘッダへのポインタ配列の、新しいメッセージを追加する位置を求める。
//{{2016/09/08変更:(unsigned)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
//	insert_pos = upper_bound((void*)msg_prio, mqhdr->mqh_mhdr, attr->mq_curmsgs, sizeof(struct msg_hdr*), msg_hdr_compar);
//↓2016/09/08変更:(unsigned)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
#ifndef _WIN64
	insert_pos = upper_bound((void*)msg_prio, mqhdr->mqh_mhdr, attr->mq_curmsgs, sizeof(struct msg_hdr*), msg_hdr_compar);			//32ビット版は、これまでと変更有りません。32ビット版も64ビット版と同じ書き方にしても動作は変わらないのですが、簡潔さを優先してこれまでのままとする事にしました。
#else //_WIN64
	insert_pos = upper_bound((void*)(uintptr_t)msg_prio, mqhdr->mqh_mhdr, attr->mq_curmsgs, sizeof(struct msg_hdr*), msg_hdr_compar);	//64ビット版は、(unsigned)(32ビット)⇒(void*)(64ビット)に直接キャストしようとすると警告が出ます。(unsigned)(32ビット)⇒(uintptr_t)(64ビット)⇒(void*)(64ビット)の順でキャストすれば警告は出ません。
#endif//_WIN64
//}}2016/09/08変更:(unsigned)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
	//このあとのmemmove()によって上書きされる位置を求める。
	remove_pos = &mqhdr->mqh_mhdr[attr->mq_curmsgs++];
	//このあとのmemmove()によって上書きされる位置の、メッセージヘッダへのポインタを取り出しておく。
	mhdr = *remove_pos;
	//新しいメッセージを追加する位置以降に有る、有効な既存のメッセージヘッダへのポインタの配列を後へずらす。
	//
	//	　　　insert_pos
	//	　　　↓　　remove_pos
	//	　　　↓　　↓
	//	■■■■■■☆□□□
	//	
	//	　　　　memmove
	//	　　　　──→
	//	■■■★■■■□□□
	//	　　　↑　　│
	//	　　　└──┘
	//
//{{2016/09/08変更:mq_send()が、(int)でアドレス演算を行っていたのを、(char*)で行うように修正しました。
//	memmove(&insert_pos[1], insert_pos, (int)remove_pos - (int)insert_pos);
//↓2016/09/08変更:mq_send()が、(int)でアドレス演算を行っていたのを、(char*)で行うように修正しました。
	memmove(&insert_pos[1], insert_pos, (char*)remove_pos - (char*)insert_pos);
//}}2016/09/08変更:mq_send()が、(int)でアドレス演算を行っていたのを、(char*)で行うように修正しました。
	//新しいメッセージを追加する位置に、memmove()によって上書きされた位置に有ったメッセージヘッダへのポインタを格納する。
	*insert_pos = mhdr;
	//メッセージヘッダを設定する。
	mhdr->msg_prio	= msg_prio;
	mhdr->msg_len	= msg_len;
	memcpy(mhdr->msg_data, msg_ptr, msg_len);
	//キューに入っているメッセージ数が変化した事を通知する。
	if(pthread_cond_signal( &mqhdr->mqh_wait)) { DIE(); }
	//メッセージキューのロックを解除する。
	if(pthread_mutex_unlock(&mqhdr->mqh_lock)) { DIE(); }
	//成功すると、mq_send()とmq_timedsend()は0を返す。
	//エラーの場合、-1を返し、errnoにエラーを示す値を設定する。	←当実装では、O_NONBLOCKを指定した場合のみ、エラーを返す可能性が有る。
	return 0;
}
/*--------------------------------------------------------------------------*/
ssize_t mq_receive(mqd_t mqdes, char* msg_ptr, size_t msg_len, unsigned* msg_prio) {
	struct mq_hdr* const mqhdr = mqdes;
	struct mq_attr* const attr = &mqhdr->mqh_attr;
	struct msg_hdr *mhdr, **insert_pos, **remove_pos;
	//メッセージキューをロックする。
	if(pthread_mutex_lock(&mqhdr->mqh_lock)) { DIE(); }
	//msg_len引き数は、msg_ptrが指すバッファーの大きさを示す。
	//この値はキューのmq_msgsize属性以上でなければならない。
	if((unsigned)msg_len < (unsigned)attr->mq_msgsize) { DIE(); }	//EMSGSIZE
	//キューが空の場合、デフォルトでは、mq_receive()は、新しいメッセージが届くか、関数呼び出しがシグナルハンドラーにより中断されるまで、停止(block)する。
	//メッセージキュー記述(message queue description)でO_NONBLOCKフラグが有効になっている場合は、mq_receive()はエラーEAGAINですぐに失敗する。
	while(!attr->mq_curmsgs) {
		if(attr->mq_flags & O_NONBLOCK) {
			//メッセージキューのロックを解除する。
			if(pthread_mutex_unlock(&mqhdr->mqh_lock)) { DIE(); }	//忘れないで!!
			errno = EAGAIN;
			return -1;
		}
		//キューに入っているメッセージ数が変化するのを待つ。
		if(pthread_cond_wait(&mqhdr->mqh_wait, &mqhdr->mqh_lock)) { DIE(); }
	}
	//このあとのmemmove()によって空きが生じる位置を求める。
	insert_pos = &mqhdr->mqh_mhdr[--attr->mq_curmsgs];
	//このあとのmemmove()によって上書きされる位置を求める。
	remove_pos = &mqhdr->mqh_mhdr[0];
	//このあとのmemmove()によって上書きされる位置の、メッセージヘッダへのポインタを取り出しておく。
	mhdr = *remove_pos;
	//取り出したメッセージの位置以降に有る、有効な既存のメッセージヘッダへのポインタの配列を前へずらす。
	//
	//	remove_pos
	//	│　　　　insert_pos
	//	↓　　　　↓
	//	★■■■■■□□□□
	//	
	//	memmove
	//	←────
	//	■■■■■☆□□□□
	//	│　　　　↑
	//	└────┘
	//
//{{2016/09/08変更:mq_receive()が、(int)でアドレス演算を行っていたのを、(char*)で行うように修正しました。
//	memmove(remove_pos, &remove_pos[1], (int)insert_pos - (int)remove_pos);
//↓2016/09/08変更:mq_receive()が、(int)でアドレス演算を行っていたのを、(char*)で行うように修正しました。
	memmove(remove_pos, &remove_pos[1], (char*)insert_pos - (char*)remove_pos);
//}}2016/09/08変更:mq_receive()が、(int)でアドレス演算を行っていたのを、(char*)で行うように修正しました。
	//memmove()によって空きが生じた位置に、memmove()によって上書きされた位置に有ったメッセージヘッダへのポインタを格納する。
	*insert_pos = mhdr;
	//戻り値を格納する。
	memcpy(msg_ptr, mhdr->msg_data, (msg_len = mhdr->msg_len));
	if(msg_prio) { *msg_prio = mhdr->msg_prio; }	//msg_prioがNULL以外の場合、msg_prioが指すバッファーに受信したメッセージの優先度が格納される。
	//キューに入っているメッセージ数が変化した事を通知する。
	if(pthread_cond_signal( &mqhdr->mqh_wait)) { DIE(); }
	//メッセージキューのロックを解除する。
	if(pthread_mutex_unlock(&mqhdr->mqh_lock)) { DIE(); }
	//成功すると、mq_receive()とmq_timedreceive()は受信したメッセージのバイト数を返す。
	//エラーの場合、-1を返し、errnoにエラーを示す値を設定する。	←当実装では、O_NONBLOCKを指定した場合のみ、エラーを返す可能性が有る。
	return msg_len;
}
/*--------------------------------------------------------------------------*/
int mq_notify(mqd_t mqdes, const struct sigevent* notification) {
	//mq_notify()は未対応であり、呼び出すとエラー停止します。
	//mq_notify()が未対応である事は、オリジナル版の実装も同じです。
	DIE();	//未対応
}
/*--------------------------------------------------------------------------*/
int mq_getattr(mqd_t mqdes, struct mq_attr* attr) {
	struct mq_hdr* const mqhdr = mqdes;
	//メッセージキューをロックする。
	if(pthread_mutex_lock(&mqhdr->mqh_lock)) { DIE(); }
	//mq_getattr()は、attrが指すバッファーにmq_attr構造体を格納して返す。
	*attr = mqhdr->mqh_attr;
	//メッセージキューのロックを解除する。
	if(pthread_mutex_unlock(&mqhdr->mqh_lock)) { DIE(); }
	//成功すると、mq_getattr()とmq_setattr()は0を返す。
	//エラーの場合、-1を返し、errnoにエラーを示す値を設定する。	←当実装ではエラーの場合プログラムを終了するので、当関数がエラーを返す事は無い。
	return 0;
}
/*--------------------------------------------------------------------------*/
int mq_setattr(mqd_t mqdes, const struct mq_attr* newattr, struct mq_attr* oldattr) {
	struct mq_hdr* const mqhdr = mqdes;
	//メッセージキューをロックする。
	if(pthread_mutex_lock(&mqhdr->mqh_lock)) { DIE(); }
	//oldattrフィールドがNULL以外の場合、mq_getattr()が返すのと同じ情報を格納したmq_attr構造体をoldattrが指すバッファーに入れて返す。
	if(oldattr) { *oldattr = mqhdr->mqh_attr; }
	//mq_setattr()は、newattrが指すmq_attr構造体で与えられた情報を使って、メッセージキューの属性を設定する。
	//変更することができる属性は、mq_flagsのO_NONBLOCKフラグの設定だけである。
	//newattrの他のフィールドは無視される。
	mqhdr->mqh_attr.mq_flags = newattr->mq_flags & O_NONBLOCK;
	//メッセージキューのロックを解除する。
	if(pthread_mutex_unlock(&mqhdr->mqh_lock)) { DIE(); }
	//成功すると、mq_getattr()とmq_setattr()は0を返す。
	//エラーの場合、-1を返し、errnoにエラーを示す値を設定する。	←当実装ではエラーの場合プログラムを終了するので、当関数がエラーを返す事は無い。
	return 0;
}
