satoh

知っているようで知らないデジタル図形処理 ~任意形状図形の選択から交点・接点・接線計算まで、方程式では解けないノウハウを紹介

   

自分自身を削除するプロセスを作ってみる

起動中の自分自身を削除してアップデートさせる…?

Windowsでは、実行中のプログラムやロード中のDLLを削除したり上書きしたりできません。
しかし、自分自身で削除して終了したり、アップデートしたりするにはどうしたらよいでしょうか?
MoveFileEx()を使って、次回のリブート時に行うことは可能です。しかしこの方法では、サーバの様に長期間稼動しっぱなしのマシンでは事実上不可能になります。また、確実に置き換えられたかは確認不可能です。
そこで、以下のような方法を考えます。

  1. 分身のモジュールを起動する。
  2. 自分は終了する。
  3. 分身のモジュールが置換や削除を行う。

しかしこのままでは、分身のモジュールがやはりディスク上に残ってしまいます。
これもクリーンアップするには以下のようにします。

  1. 分身モジュールをFILE_FLAG_DELETE_ON_CLOSEフラグでCreateFileする。
  2. 分身モジュールを起動する。
  3. 自分が終了する直前に、1.でオープンしたファイルハンドルをクローズする。

これによって、クローズ時にファイルを削除することになりますし、分身モジュール自身は終了するまでメモリ上に残ることになります。
カッコよくするために、分身モジュールは、別途プログラムを作成し、メインモジュールのリソースとしてリンクしましょう。メインモジュールはリソースから探し、これをファイル化してプロセス実行します。
以下、上記処理をまとめてみます。

メインモジュール

  1. リンク時に、分身モジュールをリソースとしてリンクする。
  2. 実行時、リソースから探し出し、ファイル化する。
  3. ファイル化した分身モジュールをFILE_FLAG_DELETE_ON_CLOSEフラグでCreateFileする。
  4. メインモジュールは自身のプロセスハンドルをOpenProcessで得る。
    このとき、SYNCHRONIZEでオープンし、分身モジュールにハンドルを継承できるようにする。
  5. 分身モジュールの起動確認のためのイベントを作成する。
  6. 分身モジュールに、4.のプロセスハンドルと5.のイベントハンドルを引数で渡し、起動する。
  7. 6.の起動確認(5.のイベントのシグナル)を待ち、3.のファイルハンドルをクローズして終了する。

分身モジュール

  1. 引数で受けたイベントハンドルをシグナル状態にする。
  2. 引数で受けたプロセスハンドルが終了するのをWaitForSingleObjectで待つ。
  3. この時点でメインモジュールは終了しているので、削除なり置換を行う。
  4. 必要に応じてメインモジュールを立ち上げるなりする。
  5. 終了する。

プログラム

では早速プログラム書いてみます。
ちょっと便利に作ってみましょう。自分自身の置き換え再起動だけでなく他プロセスの起動にも対応です。
また、分身モジュールが実行するプロセスのコマンドラインもメインモジュールから渡すようにしてあります。この場合、コマンドラインは”で囲みます。
例えば、メインモジュールは、mode == 1 で更新モジュールをネットワーク上からダウンロード、次にmode == 0 で自信の再起動に使う、などができます。
メインモジュールの関数は以下のとおりです。

extern void logWrite(char *fmt, ...) ;

#define		INNER_EXENAME		"__InnerExec"
#define		INNER_EXESTART		"__InnerExeStart_Event"
#define		INNER_EXEEND		"__InnerExeEnd_Event"

//---------------------------------------------------------------------------------------------
//      指定コマンドラインを実行
//          引数
//              1 通知モード   0:自分自身の置き換え時  1:分身からさらに別モジュール起動
//              2 実行コマンドライン(通常は自分自身で、自分自身の再起動モード)
//              3 実行モード
//---------------------------------------------------------------------------------------------
int WINAPI InnerModuleExecute(int mode, LPCSTR exec, int emode)
{
	int                     stat ;
	HRSRC                   hRsrc ;
	DWORD                   size ;
	HGLOBAL                 hGl ;
	LPVOID                  pData ;
	FILE                    *fp ;
	char                    cmdln[1024] ;
	BOOL                    bstat ;
	HANDLE                  hProcSelf, hFile ;
	STARTUPINFO             si ;
	PROCESS_INFORMATION     pi ;
	DWORD                   dstat ;
	char                    Inner_ExeName[260] ;
	char                    Inner_ExeStart[260] ;
	char                    Inner_ExeEnd[260] ;
	HANDLE                  hEvent[2] ;

	logWrite("----- Inner Module 処理開始") ;

	wsprintf(Inner_ExeName,  "%s_%08X.exe", INNER_EXENAME,  GetCurrentThreadId()) ;
	wsprintf(Inner_ExeStart, "%s_%08X",     INNER_EXESTART, GetCurrentThreadId()) ;
	wsprintf(Inner_ExeEnd,   "%s_%08X",     INNER_EXEEND,   GetCurrentThreadId()) ;

//----------------------------- リソースから置換モジュールを探す
	hRsrc = FindResource(0, INNER_RSRC_NAME, INNER_RSRC_TYPE) ;
	if (hRsrc == 0) {
		logWrite("リソースがない") ;
		return INNER_ERR_NOTFIND_RSRC ;
	}

	size = SizeofResource(0, hRsrc) ;
	if (size == 0) {
		logWrite("リソースサイズが0") ;
		return INNER_ERR_SIZE0_RSRC ;
	}

//----------------------------- リソースから置換モジュールをロードする
	hGl = LoadResource(0, hRsrc) ;
	if (hGl == 0) {
		logWrite("リソースがロードできない") ;
		return INNER_ERR_LOAD_RSRC ;
	}

//----------------------------- ロードしたリソースから置換モジュールをロックする
	pData = LockResource(hGl) ;
	if (pData == 0) {
		logWrite("LockResource失敗") ;
		return INNER_ERR_LOCK_RSRC ;
	}

//----------------------------- ファイルに書き出す
	fp = fopen(Inner_ExeName, "wb") ;
	if (fp == 0) {
		logWrite("[%s] がオープンできない", Inner_ExeName) ;
		return INNER_ERR_FILEOPEN ;
	}

	stat = fwrite(pData, size, 1, fp) ;
	if (stat != 1) {
		logWrite("ファイル書込みエラー [%s]", Inner_ExeName) ;
		fclose(fp) ;
		DeleteFile(Inner_ExeName) ;
		return INNER_ERR_FILEWRITE ;
	}

	fclose(fp) ;

//----------------------------- 置換モジュールをFILE_FLAG_DELETE_ON_CLOSEでオープンする
	hFile = CreateFile(Inner_ExeName,
						0,
						FILE_SHARE_READ,
						NULL,
						OPEN_EXISTING,
						FILE_FLAG_DELETE_ON_CLOSE,
						NULL) ;
	if (hFile == INVALID_HANDLE_VALUE) {
		logWrite("CreateFile失敗") ;
		DeleteFile(Inner_ExeName) ;
		return INNER_ERR_CREATEFILE ;
	}

//----------------------------- 自身のプロセスハンドルを得る(SYNCHRONIZEで)
	hProcSelf = OpenProcess(SYNCHRONIZE,
							TRUE,
							GetCurrentProcessId()) ;
	if (hProcSelf == 0) {
		logWrite("OpenProcess失敗") ;
		CloseHandle(hFile) ;
		return INNER_ERR_OPENPROCESS ;
	}

//----------------------------- InnerModule のコマンドライン
//                      1 通知モード
//                      2 親プロセスハンドル
//                      3 起動通知イベント
//                      4 終了イベント
//                      5 実行コマンドライン
//                      6 実行モード
	wsprintf(cmdln, "%s %d %d "%s" "%s" "%s" %d",
							Inner_ExeName,
							mode,
							hProcSelf,
							Inner_ExeStart,
							Inner_ExeEnd,
							exec,
							emode) ;

	ZeroMemory(&si, sizeof(si)) ;
	si.cb = sizeof(si) ;

//----------------------------- 起動確認イベントを作成
	hEvent[0] = CreateEvent(0, TRUE, FALSE, Inner_ExeStart) ;
	if (hEvent[0] == 0) {
		logWrite("CreateEvent 失敗") ;
		CloseHandle(hProcSelf) ;
		CloseHandle(hFile) ;
		return INNER_ERR_CREATEEVENT ;
	}

	hEvent[1] = CreateEvent(0, TRUE, FALSE, Inner_ExeEnd) ;
	if (hEvent[1] == 0) {
		logWrite("CreateEvent 失敗") ;
		CloseHandle(hProcSelf) ;
		CloseHandle(hFile) ;
		CloseHandle(hEvent[0]) ;
		return INNER_ERR_CREATEEVENT ;
	}

//----------------------------- 置換モジュールを起動する
	logWrite("CreateProcess [%s]", cmdln) ;
	bstat = CreateProcess(0,
						  cmdln,
						  0,
						  0,
						  TRUE,
						  0,
						  NULL,
						  NULL,
						  &si,
						  &pi) ;
	if (!bstat) {
		logWrite("CreateProcess失敗") ;
		CloseHandle(hProcSelf) ;
		CloseHandle(hFile) ;
		CloseHandle(hEvent[0]) ;
		CloseHandle(hEvent[1]) ;
		return INNER_ERR_CREATEPROCESS ;
	}

//----------------------------- 置換モジュール起動を確認する
	dstat = WaitForMultipleObjects(2, hEvent, FALSE, INFINITE) ;
	switch (dstat) {
		case    WAIT_OBJECT_0:
		case    WAIT_ABANDONED_0:
			stat = 0 ;
			break ;
		case    WAIT_OBJECT_0 + 1:
		case    WAIT_ABANDONED_0 + 1:
			while (1) {
				GetExitCodeProcess(pi.hProcess, (LPDWORD)&stat) ;
				if (stat != STILL_ACTIVE) break ;
				Sleep(100) ;
			}
			break ;
		default:
			logWrite("WaitForSingleObject 失敗") ;
			CloseHandle(pi.hProcess) ;
			CloseHandle(pi.hThread) ;
			CloseHandle(hProcSelf) ;
			CloseHandle(hFile) ;
			CloseHandle(hEvent[0]) ;
			CloseHandle(hEvent[1]) ;
			return INNER_ERR_WAITFOR ;
	}
	logWrite("[%s] 起動", Inner_ExeName) ;

//----------------------------- プロセスハンドルとファイルハンドルをクローズする
	CloseHandle(pi.hProcess) ;
	CloseHandle(pi.hThread) ;
	CloseHandle(hProcSelf) ;
	CloseHandle(hFile) ;
	CloseHandle(hEvent[0]) ;
	CloseHandle(hEvent[1]) ;

	logWrite("----- Inner Module 処理終了") ;

	return stat ;
}

分身モジュールは以下の通りです。

//---------------------------------------------------------------------------------------------
//      指定コマンドラインを実行
//          引数
//              1 通知モード   0:自分自身の置き換え時  1:分身からさらに別モジュール起動
//              2 親プロセスハンドル
//              3 起動通知イベント
//              4 終了イベント
//              5 実行コマンドライン
//              6 実行モード
//---------------------------------------------------------------------------------------------
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPTSTR lpCmdln, int nCmdShow)
{
	HANDLE                  hProc ;
	int                     stat ;
	DWORD                   dstat ;
	BOOL                    bstat ;
	char                    buff[1024] ;
	HANDLE                  hEvent ;
	char                    cmdln[1024] ;
	int                     mode, emode ;
	STARTUPINFO             si ;
	PROCESS_INFORMATION     pi ;
	int                     i, len ;

//------------------------ 実行コマンドラインの''で囲まれている部分を""に変更する
	len = lstrlen(__argv[5]) ;
	for (i = 0 ; i < len ; i++) {
		if (__argv[5][i] == ''') {
			cmdln[i] = '"' ;
		} else {
			cmdln[i] = __argv[5][i] ;
		}
	}
	cmdln[len] = 0 ;

//------------------------ 通知モード 0:最初 1:最後
	mode = atoi(__argv[1]) ;

//------------------------ 実行モード 0:Default 1:Icon 2:Hide
	emode = atoi(__argv[6]) ;

	if (mode == 0) {
//------------------------ 親プロセスハンドル
		hProc = (HANDLE)atol(__argv[2]) ;

		hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, __argv[3]) ;
		if (hEvent == 0) {
			return 99 ;
		}

		SetEvent(hEvent) ;
		CloseHandle(hEvent) ;

		dstat = WaitForSingleObject(hProc, INFINITE) ;
		if (dstat != WAIT_OBJECT_0) {
			return 99 ;
		}

		CloseHandle(hProc) ;

	}

	ZeroMemory(&si, sizeof(si)) ;
	si.cb = sizeof(si) ;

	switch (emode) {
		case    1:
			si.dwFlags     = STARTF_USESHOWWINDOW ;
			si.wShowWindow = SW_MINIMIZE ;
			break ;
		case    2:
			si.dwFlags     = STARTF_USESHOWWINDOW ;
			si.wShowWindow = SW_HIDE ;
			break ;
		default:
			si.dwFlags     = 0 ;
			si.wShowWindow = SW_SHOWDEFAULT ;
			break ;
	}

	bstat = CreateProcess(0,
						  cmdln,
						  0,
						  0,
						  TRUE,
						  0,
						  NULL,
						  NULL,
						  &si,
						  &pi) ;

	if (!bstat) return 99 ;

	stat = 0 ;

	if (mode == 1) {

		while (1) {
			GetExitCodeProcess(pi.hProcess, (LPDWORD)&stat) ;
			if (stat != STILL_ACTIVE) break ;
			Sleep(100) ;
		}

		hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, __argv[4]) ;
		if (hEvent == 0) {
			return 99 ;
		}

		SetEvent(hEvent) ;
		CloseHandle(hEvent) ;
	}

	return stat ;
}

 - C, Win32API, Windows