Virtual PC上のCentOSの時刻を同期する

備忘録(メモ)のエントリです。

会社で使用しているPC(WindowsXP SP3)にローカル・テスト環境が欲しくて、Virutal PC 2007 に CentOS5 を入れて使用しています。導入したときは、いろいろ分からないことがあり、ネット上でその都度調べたりして、Virtual Serverではないのでログオン時に手動で起動しなければならない・・・こと以外は特に問題もなく安定動作してきました。
 

いえ、安定動作していると、ちょっと前までそう思い込んでいただけのようです。。。
 

「時刻がスゲェ~ずれている・・・」 50分以上も・・・。
 

いえ、もちろん、時刻合わせのため、1時間毎に ntpdate を実行するように crond に登録済み・・・なのですが・・・それでも・・・

「時刻がスゲェ~ずれている・・・」 のです。。。
 

なんで?
 

ネットで検索したら、マイクロソフトが配布しているLinux向けの修正?を導入すると、時刻同期が取れるようです。しかも・・・これは、FAQのようです。。。お恥ずかし・・・。
でも、クリック一発でインストール・・・できるはずもなく、いろいろ試行錯誤した結果、やっと CentOS 5(on VirtualPC 2007)が正しく時を刻んでくれるようになりました。。。
 

前準備

マイクロソフトのダウンロードセンター(英語)から、下記のファイルをダウンロードする。
 

Virtual Machine Additions for Linux
 

ダウンロードしたインストーラ(msiファイル)を実行すると、%ProgramFiles%Microsoft Virtual ServerVirtual Machine Additions ディレクトリにISOファイルが出来ています。まず、それをVirtualPC 2007のメニュー >> CD >> ISOイメージのキャプチャ、する。
 

なお、このファイルは本来CentOSは動作対象外ですが、RHEL5が動作対象に入っているので問題ないでせう。

ゲストOS(CentOS)での作業

ゲストOS(CentOS5)上で、以下を実行

前準備とリンクの張りなおし?

$ su -
$ yum install gcc
$ yum install kernel-devel

$ cd /lib/modules/2.6.18-164.15.1.el5
$ rm build
$ ln -s ../../../usr/src/kernels/2.6.18-194.8.1.el5-i686 build

なにわともわれ、ルートユーザで作業です。
で、よく分からんけど、Linuxカーネルをビルドする環境が必要なんだとか。
 

真の?Linuxユーザーではないので、カーネルモジュールのビルドとか、RPMとか言われてもチンプンカンプン。
・・・で、buildっていうシンボリックリンクが、存在しないディレクトリを指していて、一旦削除して、リンクしなおしてます。
 

で、次です。

$ mount -o ro /dev/dvd /media
$ rpm -ivh vmadd-kernel-module-RHEL-2.0-1.i386.rpm
$ rpm -ivh vmadd-timesync-2.0-1.i386.rpm
$ rpm -ivh vmadd-heartbeat-2.0-1.i386.rpm
$ rpm -ivh vmadd-shutdown-2.0-1.i386.rpm

前準備でVirtualPCでキャプチャしておいたISOファイルをマウントして、その中にあるRPMパッケージをインストールします。僕の場合は、X-Window関連とSCSI関連のものは必要ないので、省いています。時刻を同期させるためだけなら、kernel-module-RHELとtimesyncだけで良さそうですが・・・なんとなく他の二つも入れときます。どういう役割かは、ググれば出てきそう。。。あんまり関係なさそうな感じもします。
 

ここまで作業終了。
起動確認します。

$ service vmadd start
$ service vmadd-timesync start
$ service vmadd-shutdown start
$ service vmadd-heartbeat start

エラーが出なかったら終了。

一応確認。

$ chkconfig --list | grep '^vmadd' 

rebootコマンドで再起動して再確認。
 

Linuxについて詳しいパワーユーザーなら、なんてことない作業だと思いますが・・・
あーでもない、こーでもない、と午前中はずっとコレばっかり調べては、トライして、失敗。
結局、午前中いっぱい昼休憩までかかりました(ーー;;
 

ですが、おかげでさまで、やっと仮想環境内のCentOSは正確に時間を刻むようになりましたとさ。
 

・・・それと、今までは、CentOSを「状態を保存して終了」してから、「再開」すると、再開するまでの間、ずっと時計が止まったままになるので(当たり前か!)、かならず poweroff コマンドで終了してたんですが、試しに・・・、

1.「状態を保存して終了」
2. 数分待つ。
3.「再開 」

すると、ちゃんと時刻の同期が取れてるぞ! お、これは便利だ・・・。これでPCの電源を落とすとき、いちいちPoweroffコマンドをうつ手間が省けます~~ぅ。

コマンドプロンプトのログを取る(その3)

コマンドプロンプトのログを取る(その2)
コンソール(CMD.EXE)のログを取る (不完全版)
の続きです。

その2で、僕が望んだ動作はほぼ達成できました。だいぶ不完全だけど・・・。
その2の不満点は、CTRL-Cを押すとcmdlog.exe自体がガサッと落ちてしまうんです(子プロセス諸とも終了してしまう)。やはり、CTRL-Cを押すと、子プロセス側で走っているコンソールプロセスだけ死んで欲しいのは当然ですよねぇ。
ってことで、こういう場合は、親プロセス(cmdlog.exe)がCTRL-Cを受け取ったら、親プロセス側は何もせず、CTRL-Cイベント(シグナルかな?)をそのまま子プロセスに渡してしまうのがお約束かとおもいます。

で、そのまんまのSetConsoleCtrlHandlerというAPIがありますので、これを使います。

このAPIは名前のとおり、コンソールプロセスでCtrl-CとかCtrl-Homeを受け取ったときに呼び出される関数をセットできるもので、既存のハンドラルーチン(関数)を追加、削除ができます。
なわけで、

/*CTRLハンドラ*/
BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
{
  BOOL bRetVal = FALSE;

  if(dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT)
    bRetVal = GenerateConsoleCtrlEvent(dwCtrlType,Context::ProcessInformation.dwProcessId);
  
  return bRetVal;
}

のようなシグナルハンドラ(っていうのかな?)を追加すれば、Ctrl-Cを子プロセスに送って自分(親プロセス)は何もしないってことができます。良かった。パチパチ。

ってなわけで・・・

若干手直ししたソースはこちらから。。。(cmdlog03.zip)
全てのソースはこっちに移動

あとは・・・そうですね・・・いいかげん、コマンド引数からログファイル名を指定するようにせんといかんな・・・ハードコードなんてダサすぎる・・・(ーー;;;


コマンドプロンプトのログを取る(完結)
コマンドプロンプトのログを取る(その3)
コマンドプロンプトのログを取る (その2)
コンソール(CMD.EXE)のログを取る (不完全版)

コマンドプロンプトのログを取る (その2)

コンソール(CMD.EXE)のログを取る (不完全版) の続きです。

標準出力と標準エラー出力をファイルに書き出せたところまではできてタイムアップ。標準入力が書き出せていませんでした。今回は、入力バッファをファイルに書き出してから、リダイレクトしたパイプハンドルに書き出せば事は足ります。

で、あまりにもソースが汚くなってきたので整理して、テストコードが完成。パイプハンドルをラップした構造体とスレッドに渡すコンテキストを別ファイルにして整理。

/*******************************************************************

 ■コマンドプロンプトのすべての操作をテキストファイルへ書き出します。

  ※ほとんどエラーチェック・処理をしていないテストコード
  ※書き出すファイルは、#define LOGFILE TEXT("~") で指定。

********************************************************************/

#include <windows.h>

//Cランタイム
#include <tchar.h>
#include <stdio.h>

//パイプ、スレッドに渡すパラメータをラップした構造体を定義したヘッダー
#include "pipe.h"
#include "context.h"

#define CLOSEHANDLE(X) {if(X)CloseHandle(X);}
#define BUFFER_SIZE 1024
#define SPIN_COUNT 4000

//書き出すファイル名の指定
#define LOGFILE TEXT("cmdlog.txt")

//標準入出力・ファイルハンドルへの読取と書込スレッド
DWORD WINAPI ReadAndWriteProc(LPVOID lpParameter)
{
  Context *context = (Context*)lpParameter;

  TCHAR pBuffer[BUFFER_SIZE] = {0};
  DWORD dwRead = 0,dwWrite = 0;
  BOOL bRet = FALSE;
  while(!(context->bThreadMustTerminate))
    {
      do
        {
          bRet = ReadFile(context->hRead,(PVOID)pBuffer,BUFFER_SIZE,&dwRead,NULL);
          if(dwRead > 0)
            {
              EnterCriticalSection(&(context->CriticalSection));
              if(context->hFile)
                WriteFile(context->hFile,(PVOID)pBuffer,dwRead,&dwWrite,NULL);
              if(context->hWrite)
                WriteFile(context->hWrite,(PVOID)pBuffer,dwRead,&dwWrite,NULL);
              LeaveCriticalSection(&(context->CriticalSection));
            }
        }
      while(bRet == TRUE && dwRead > 0);
    }

  return 0;
}

//ファイルを書込用にオープン(同期)
HANDLE OpenLogFile(PCTSTR szLogName)
{
  return CreateFile(szLogName,
                    GENERIC_WRITE,
                    0,
                    NULL,
                    CREATE_ALWAYS,
                    FILE_ATTRIBUTE_NORMAL,
                    NULL);
}

//環境変数COMSPECからコマンドプロンプトのパスを得て起動
BOOL CreateCommandPrompt(STARTUPINFO& si,PROCESS_INFORMATION& pi)
{
  BOOL bRetVal = FALSE;

  //CMD.EXEのパスを格納するバッファ
  TCHAR szCmdPath[MAX_PATH+1] = {0};

  //CMD.EXEのパスを環境(システム)変数から得る。
  if(0 == GetEnvironmentVariable(TEXT("COMSPEC"),szCmdPath,MAX_PATH+1))
    goto cleanup;
  
  bRetVal = CreateProcess(NULL,
                          szCmdPath,
                          NULL,
                          NULL,
                          TRUE,
                          NORMAL_PRIORITY_CLASS,
                          NULL,
                          NULL,
                          &si,
                          &pi);

cleanup:
  return bRetVal;
}

//スタートアップ
int _tmain(int argc,_TCHAR **argv)
{
  UNREFERENCED_PARAMETER(argc);
  UNREFERENCED_PARAMETER(argv);
  
  /*------------------------------------------------------------
    スレッドハンドル、パイプ、スレッドに渡すコンテキスト
    配列はそれぞれ、0:標準入力用 1:標準出力用 2:標準エラー出力用
  --------------------------------------------------------------*/
  HANDLE hThreads[3] = {NULL};
  Pipe Std[3] = {PIPE_READ_INHERIT,PIPE_WRITE_INHERIT,PIPE_WRITE_INHERIT};
  Context ctx[3];

  //ログを書き込むファイル
  HANDLE hFile = NULL;
  
  //CreateProcessに必要な構造体
  STARTUPINFO si = {sizeof(STARTUPINFO)};
  PROCESS_INFORMATION pi = {NULL};

  //クリティカルセクション初期化
  InitializeCriticalSectionAndSpinCount(&Context::CriticalSection,SPIN_COUNT);

  if((hFile = OpenLogFile(LOGFILE)) == INVALID_HANDLE_VALUE)
    {
      _tprintf(TEXT("ログファイルのオープンが失敗しました"));
      goto cleanup;
    }

  //入出力ハンドルをリダイレクトしたコマンドプロンプトを生成
  si.dwFlags    = STARTF_USESTDHANDLES;
  si.hStdInput  = Std[0].hRead;   //パイプにリダイレクト
  si.hStdOutput = Std[1].hWrite; //パイプにリダイレクト
  si.hStdError  = Std[2].hWrite; //パイプにリダイレクト

  if(FALSE == CreateCommandPrompt(si,pi))
    {	
      _tprintf(TEXT("CreateProcess 失敗 : %d"),GetLastError());
      goto cleanup;
    }

  //コンテキスト構造体のメンバをセット
  ctx[0].Set(GetStdHandle(STD_INPUT_HANDLE),Std[0].hWrite);
  ctx[1].Set(Std[1].hRead,GetStdHandle(STD_OUTPUT_HANDLE),hFile);
  ctx[2].Set(Std[2].hRead,GetStdHandle(STD_ERROR_HANDLE),hFile);
  
  //スレッド作成
  for(int i=0;i<3;i++)
    hThreads[i] = CreateThread(NULL,0,ReadAndWriteProc,&ctx[i],0,NULL);
  
  //新しいコマンドプロンプトが終了するまで待機
  WaitForSingleObject(pi.hProcess,INFINITE);
  
  //スレッド終了通知をセット
  Context::bThreadMustTerminate = TRUE;

  //パイプハンドルのクローズ
  for(int i=0;i<3;i++)
    Std[i].Close();

  CloseHandle(GetStdHandle(STD_INPUT_HANDLE));

  //起動したスレッドが終了するまで待機
  WaitForMultipleObjects(3,hThreads,TRUE,INFINITE);

  //クリティカルセクションオブジェクトを削除
  DeleteCriticalSection(&Context::CriticalSection);

cleanup:
  //ハンドルを閉じる
  CLOSEHANDLE(hFile);
  CLOSEHANDLE(pi.hThread);
  
  for(int i=0;i<3;i++)
    CLOSEHANDLE(hThreads[i]);
  
  CLOSEHANDLE(pi.hProcess);

  return 0;
}

すべてのコードを固めたものはこちら。
全てのソースはこっちに移動
nmakeでcmdlog.exeが生成されます。

ただ、これは CTRL-Cとかすると・・・コマンドプロンプト自体がガサッと終了してしまいますし、入力した内容がエコーされるし(ファイルには出力されないので別に良いのですが・・・)、まだまだ不完全。
書き出すファイルパスはハードコードしとるし、エラー処理していないし、ファイルの書き出しは同期処理なので、遅いデバイスを指定すると(今更ないと思うけどフロッピーとか)書出処理から制御が戻ってこないので非常にマズイことになってます・・・。

また暇なときに完成度を高めたい・・・といっててそのままでずっと使うことが多いんですが(ーー;


コンソール(CMD.EXE)のログを取る (不完全版)
コマンドプロンプトのログを取る(その2)
コマンドプロンプトのログを取る(その3)
コマンドプロンプトのログを取る(完結)

壊れた?

今日、デスクトップPCを立ち上げたら、唐突に chkdsk が走りました。
エラーもなく終了して、起動してイベントビューアをチェックしたら・・・

「ディスクのファイル システム構造は壊れていて使えません。」

えッ・・・?

今のHDD、買って半年経ってないんですけど・・・

もう一回、chkdsk を走らせて見るも、エラーなし。

とくに壊れる兆候はないのですが・・・ん~・・・まぁ、データは別ドライブなので最悪再インストールすればいいのですが・・・。

う~ん・・・。

バックアップとリカバリと再セットアップとリストア

VAIOノートをリカバリ中です。。。時間かかるわ(ーー;;

VAIOリカバリ

VAIOノートには、Windows7 RC版が入っているんですが・・・、このRC版は3月に入ると、2時間おきにシャットダウンが繰り返すようになるので、入院前に製品版へ移行しておこうと・・・いうわけです(^^;;; 2月中には退院できるとは思いますが・・・なんかあってもし入院が3月まで伸びてしまったら面倒なので・・・。

メインのデスクトップ用にパッケージ版のWindows7があるので、プロダクトキーを購入すれば・・・と思って探したんですが・・・Windows7ってプロダクトキーだけの販売はないみたいですね。。。主に法人向けのライセンス販売はあるようですが・・・(個人でも購入自体はOKみたいですけど)

パッケージ版を買っても「燃えないゴミ」が増えるだけだし・・・と思い、MSサイトでダウンロード版を買うことにしました。インストールディスクはすでにあることだし、ダウンロードもする必要もない。こりゃ、いいや、ということで、早速決済。

で・・・まだ、リカバリが終わりません(^^;;;
リカバリが終わって、Vista Service Pack2を適用して、要らないソフトを削除して、7のセットアップして、使うツールとアプリをインストールして、各種サーバーを入れて、検証して、データを戻して、・・・・ああ、久しぶりに、徹夜コースか。。。(ーー;;;