TT プログラマ記

(2008.03.01)

[nyaos][Windows] Windows で多段パイプライン実装

おひさ。

仕事でプロセスの実行をするプログラムの開発をしました。 その経緯で Unix 系 OS で言う fork() とか exec() とか とか dup2() の使い方を覚えまして *1、まあ Windows 製品もあるのでその勉強もしたんですね。

んで、 fork() とか exec() に関して検索とかして調べると当然こんなページも見つかるのですよ。

NYAOS が答える――どうして fork と exec は別々なのか

これを見て「Windows でもちゃんとしたパイプ実装できるんじゃね?」と思った訳で、ちょいと殴り書きだけど作ってみた。 mysystem.cpp をこれで上書きすればいける筈。これで行けそうならちゃんと綺麗に書き直したいかな。

けど、元々のソースでも一応(作者は汚いと言ってるけど)多段パイプはできているし、 これもちょっと汚い部分あるし。 (一度継承可能で作ったパイプの読込側を継承不可にして、前コマンドを実行した後、継承可能にして後コマンドを実行している) どうなんだろう。これが正しい Windows の多段パイプ実装なのかな。

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <io.h>
#include <dos.h>
#include <string.h>

#include "config.h"

#include <windows.h>

#include "nnstring.h"
#include "nnvector.h"
#include "nnhash.h"
#include "writer.h"
#include "nndir.h"
#include "shell.h"

#define DUP_STD_HANDLE( type, n ) DuplicateHandle( GetCurrentProcess(),\
                                                   GetStdHandle( type ),\
                                                   GetCurrentProcess(),\
                                                   n, 0, TRUE,\
                                                   DUPLICATE_SAME_ACCESS )
#define SET_INHERIT_FLAG( handle, flag ) DuplicateHandle( GetCurrentProcess(),\
                                                          handle,\
                                                          GetCurrentProcess(),\
                                                          NULL, 0, flag,\
                                                          DUPLICATE_SAME_ACCESS )


enum{
  TOO_LONG_COMMANDLINE = -3  ,
  MAX_COMMANDLINE_SIZE = 127 ,
};

static HANDLE mySpawn( const NnString &execstr, HANDLE input, HANDLE output, HANDLE error_out )
{
  char* _tmp = (char*)malloc( sizeof( char* ) * strlen( execstr.chars() ) + 1 );
  strcpy( _tmp, execstr.chars() );

  STARTUPINFO info ={0};
  info.cb = sizeof( info );
  info.dwFlags = STARTF_USESTDHANDLES;
  info.hStdInput  = input;
  info.hStdOutput = output;
  info.hStdError  = error_out;

  PROCESS_INFORMATION proc = {0};

  if ( CreateProcess( NULL, _tmp, NULL, NULL, TRUE,
                      0, NULL, NULL, &info, &proc ) ) {
    CloseHandle( proc.hThread );
  }
  else {
    proc.hProcess = INVALID_HANDLE_VALUE;
  }
  free( _tmp );
  return proc.hProcess;
}

static void parseRedirect( const char*& cmdline, HANDLE& h )
{
  const char *mode="w";
  if( *cmdline == '>' ) {
    mode = "a";
    ++cmdline;
  }
  NnString path;
  NyadosShell::readNextWord( cmdline , path );
  h = (HANDLE)_get_osfhandle( NnDir::open( path.chars(), mode ) );
  if ( h == INVALID_HANDLE_VALUE ) {
    conErr << path << ": can't open.\n";
  }
}

static void after_lessthan( const char *&sp , HANDLE& h )
{
  if( sp[0] == '&'  &&  isdigit(sp[1]) ){
    /* n>&m */
    DuplicateHandle( GetCurrentProcess(),
                     (HANDLE)_get_osfhandle( sp[1] - '0' ),
                     GetCurrentProcess(),
                     &h, TRUE, 0, DUPLICATE_SAME_ACCESS );
    sp += 2;
  }
  else if( sp[0] == '&' && sp[1] == '-' ) {
    /* n>&- */
    h = (HANDLE)_get_osfhandle( NnDir::open( "nul", "w" ) );
  }
  else {
    /* n> ファイル名 */
    parseRedirect( sp, h );
  }
}


static HANDLE do_one_command( HANDLE input, HANDLE output, const char* cmdline ) {
  NnString execstr;
  HANDLE error_out;

  class HandleCloser {
  private:
    HANDLE _h;
  public:
    HandleCloser() : _h( INVALID_HANDLE_VALUE ) {}
    HandleCloser( HANDLE h ) : _h( h ) {}
    ~HandleCloser() { this->close(); }
    void set( HANDLE h ) {
      this->close();
      _h = h;
    }
    void close() {
      if ( _h != INVALID_HANDLE_VALUE ) {
        CloseHandle( _h );
        _h = INVALID_HANDLE_VALUE;
      }
    }
  };
  HandleCloser input_closer;
  HandleCloser output_closer;
  HandleCloser error_closer( error_out );

  DUP_STD_HANDLE( STD_ERROR_HANDLE, &error_out );
  SET_INHERIT_FLAG( error_out, TRUE );

  int quote = 0;
  while( *cmdline != '\0' ) {
    if( *cmdline == '"' ) {
      quote ^= 1;
    }

    if( quote==0 && cmdline[0]=='<' ) {
      ++cmdline;
      NnString path;
      NyadosShell::readNextWord( cmdline , path );
      input = (HANDLE)_get_osfhandle( NnDir::open( path.chars(), "r" ) );
      if ( input == INVALID_HANDLE_VALUE ) {
        conErr << path << ": can't open.\n";
      }
      SET_INHERIT_FLAG( input, TRUE );
      input_closer.set( input );
    }
    else if( quote==0 && cmdline[0]=='>' ) {
      ++cmdline;
      parseRedirect( cmdline, output );
      output_closer.set( output );
    }
    else if( quote==0 && cmdline[0]=='1' && cmdline[1]=='>' ) {
      cmdline += 2;
      after_lessthan( cmdline, output );
      output_closer.set( output );
    }
    else if( quote==0 && cmdline[0]=='2' && cmdline[1]=='>' ) {
      cmdline += 2;
      after_lessthan( cmdline , error_out );
      error_closer.set( output );
    }
    else {
      execstr << *cmdline++;
    }
  }
  return mySpawn( execstr, input, output, error_out );
}


int mySystem( const char *cmdline )
{
  if ( cmdline[0] == '\0' ) {
    return 0;
  }

  /*
  NnString *mode = (NnString*)properties.get( "standalone" );
  if ( mode == NULL || mode->length() <= 0 ) {
    errno = 0;
    int rc = system( cmdline );
    if ( errno != 0 ) {
      perror( SHELL_NAME );
      return -1;
    }
    return rc;
  }
   */

  NnVector pipeSet;
  { // devidePipes
    NnString one,left( cmdline );
    while( left.splitTo(one,left,"|") , !one.empty() ) {
      while ( one.chars()[0] == ' ' || one.chars()[0] == '\t' ) {
        one.shift();
      }
      pipeSet.append( one.clone() );
    }
  }

  HANDLE prev_pipe_read;
  HANDLE next_pipe_write;

  DUP_STD_HANDLE( STD_INPUT_HANDLE, &prev_pipe_read );

  int result;
  
  for ( int i = 0; i < pipeSet.size(); ++i ) {
    if( i < pipeSet.size() - 1 ) {
      /* パイプラインの末尾でないコマンドの場合、
       * 標準出力の先を、パイプの一方にする必要がある
       */
      HANDLE next_pipe_read;
      SECURITY_ATTRIBUTES sa;
      sa.nLength = sizeof( sa );
      sa.lpSecurityDescriptor = NULL;
      sa.bInheritHandle = TRUE;
      CreatePipe( &next_pipe_read, &next_pipe_write, &sa, 0 );
      SET_INHERIT_FLAG( next_pipe_read, FALSE );
      SET_INHERIT_FLAG( prev_pipe_read, TRUE );
      do_one_command( prev_pipe_read,
                      next_pipe_write,
                      ((NnString*)pipeSet.at( i ))->chars() );
      CloseHandle( prev_pipe_read );
      CloseHandle( next_pipe_write );
      prev_pipe_read = next_pipe_read;
    }
    else {
      DUP_STD_HANDLE( STD_OUTPUT_HANDLE, &next_pipe_write );
      SET_INHERIT_FLAG( prev_pipe_read, TRUE );
      HANDLE last_proc = do_one_command( prev_pipe_read,
                                         next_pipe_write,
                                         ((NnString*)pipeSet.at( i ))->chars() );
      CloseHandle( prev_pipe_read );
      CloseHandle( next_pipe_write );

      if ( last_proc != INVALID_HANDLE_VALUE ) {
        WaitForSingleObject( last_proc, INFINITE );
        GetExitCodeProcess( last_proc, (unsigned long*)&result );
      }
      else {
        result = -1;
      }

      if( result < 0 ) {
        if( ((NnString*)pipeSet.at(i))->length() > 110 ){
          conErr << "Too long command line,"
            " or bad command or file name.\n";
        }
        else {
          conErr << "Bad command or file name.\n";
        }
      }
    }
  }
  return result;
}

*1 前から勉強はしてたけど

ツッコミ、もしくは、リンク元

(2007.09.12)

だいぶ放置してしまった。

[C++][Lisp] TtCppLisp を公開した。

という訳で以前に言っていた C++ で実装された Lisp 処理系である TtCppLisp を公開しました。

6 月あたりに殆ど実装済みで、そのへんで満足しちゃってほっといたんだよね。 もったいなか。せっかくなのでいろいろ整理して公開と。

全部で 5000 行ぐらい。そこそこコンパクトなのかしら?まあ世の中 500 行とかあるし全然駄目かも。 けどそれなりに機能は実装済み。GC とかクロージャとかあるし、 氾濫する lisp の処理系の中ではちゃんとしてるんじゃないかなあと。 そりゃあちゃんとしたのに比べれば月とスッポンだけどもさ。

あーっと、テストは全然していないのでバグバグだと思う。まあ、一応機能に対して 2, 3 回程度のテストはしてるけど。

まあ、作ってみた感想はまた今度でも。

ツッコミ、もしくは、リンク元

基本的なことですみませんが、MSC6.0でコンパイルするにはどのようにしたらよいでしょか (2007/11/06 18:56:21)

TEM@管理者 UtilityFunctions.cpp で #include <limits> とstd::min に修正してコンパイラオプションに -GX と -GR をつければwarning は出ますがコンパイルはとおります。が、標準入力の動作がおかしい為ちゃんと動作しないですね。 (2007/11/07 00:23:52)

ありがとうございます。少し、追いかけてみます。 (2007/11/07 18:36:01)

(2007.05.13)

仕事の残業が 100 時間を超えて注意されてしまったよ。

[C++] TtCppLisp

TtLisp を C++ で実装する試みは実行中。 結構いい感じにできてきました。 実装するのに特に苦労したのが GC とパーサ。

GC は C++ のポインタの扱いとか知らなかったとかいろいろ有ったけど、 Ruby の GC を参考に実装できていい感じで動いてくれるので結構満足。 やっぱりプログラマなら 1 回ぐらいは GC を実装してみたいよね!でへぇ。

パーサはこれがまたかなり苦労したというか、 この辺のスキルが無いのに継続可能な仕様にしたのがまずかったみたいで、 かなりの試行錯誤をしまくりだった。 というかまだ完成してないし。文字列のエスケープシーケンスとかスゲー面倒だったよう。 コードもいまいち感がたっぷりだしねぇ。そりゃパーサジェネレータなんて物ができるわな。

あとは残った細かい部分を埋めていけば TtLisp と同等のはできそう。そしたら公開できるな。

ツッコミ、もしくは、リンク元