OS2/emx/libc/popen/full_duplex_popen

OS2/emx/libc/popen/full_duplex_popen

双方向リダイレクト可能な popen(もどき)をためしに作成。

/*
    2006-01-04 emx libc の popen あたりを参考に試作
    2006-01-11 けっこうバグってたので直した
*/
#if defined(__EMX__) && !defined(__ST_MT_ERRNO__)
#define __ST_MT_ERRNO__
#endif
#include <errno.h>
#include <fcntl.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


static
char *
my_getshell(char **optstr)
{
    char *s = NULL;
    char *bn, *ops;
    
#if 0
    s = getenv("RUBYSHELL");
#endif
#if defined(__EMX__)
    if (!s) s = getenv("EMXSHELL");
#endif
    if (!s) s = getenv("SHELL");
    if (!s) s = getenv("COMSPEC");
    if (!s) s = "cmd.exe";
    bn = _getname(s);
    if (stricmp(bn, "cmd.exe") == 0
#if defined(OS2) || defined(__OS2__)
        || stricmp(bn, "4os2.exe") == 0
#endif
        || stricmp(bn, "command.com") == 0
        )
        ops = "/c";
    else
        ops = "-c";
    
    if (optstr) *optstr = ops;
    return s;
}

int
emx_duplex_popen(const char *cmd, const char *fmode, FILE **fprd, FILE **fpwr)
{
    FILE  *fr = NULL, *fw = NULL;
    char  fmr[4], fmw[4];
    int  pdr[2], pdw[2];
    int  pid_child = -1;
    int  ph_stdin = -1, ph_stdout = -1;
    int  pm_stdin = -1, pm_stdout = -1;
    int  prev_errno;
    char *sh, *shopt;
    enum { MY_FMODE_DEFAULT = 0, MY_FMODE_TEXT, MY_FMODE_BINARY }
        my_fmode = MY_FMODE_DEFAULT;
    char  c;
    
    sh = my_getshell(&shopt);
    
    fmr[0] = fmr[1] = fmw[0] = fmw[1] = '\0';
    if (!fmode || !(fmode[0] == 'r' || fmode[0] == 'w')) {
        errno = EINVAL;
        return -1;
    }
    while((c = *fmode++) != '\0') {
        switch(c) {
          case 'r':  fmr[0] = c; break;
          case 'w':  fmw[0] = c; break;
          case 'b':  my_fmode = MY_FMODE_BINARY; break;
          case 't':  my_fmode = MY_FMODE_TEXT; break;
          case '+':
            fmr[0] = 'r';
            fmw[0] = 'w';
            break;
        }
    }
    if (my_fmode != MY_FMODE_DEFAULT) {
       fmr[1] = fmw[1] = (my_fmode == MY_FMODE_TEXT) ? 't' : 'b';
       fmr[2] = fmw[2] = '\0';
    }
    
    if (*fmr) {
        if (pipe(pdr) < 0) return -1;
        if (fcntl(pdr[0], F_SETFD, FD_CLOEXEC) == -1 ||
            fcntl(pdr[1], F_SETFD, FD_CLOEXEC) == -1)
        {
            return -1;
        }
    }
    if (*fmw) {
        if (pipe(pdw) < 0) return -1;
        if (fcntl(pdw[0], F_SETFD, FD_CLOEXEC) == -1 ||
            fcntl(pdw[1], F_SETFD, FD_CLOEXEC) == -1)
        {
            return -1;
        }
    }
    
    /* ATOMIC 制御いれるならこのへんあたりから? */

    if (*fmr) { /* parent pipe-in <- child stdout */
        pm_stdout = fcntl(STDOUT_FILENO, F_GETFD, 0);
        ph_stdout = dup(STDOUT_FILENO);
        fcntl(ph_stdout, F_SETFD, FD_CLOEXEC);
        dup2(pdr[1], STDOUT_FILENO);
        close(pdr[1]);
        fr = fdopen(pdr[0], fmr);
        if (fr) setbuf(fr, NULL);
    }
    if (*fmw) { /* parent pipe-out -> child stdin */
        pm_stdin = fcntl(STDIN_FILENO, F_GETFD, 0);
        ph_stdin = dup(STDIN_FILENO);
        fcntl(ph_stdin, F_SETFD, FD_CLOEXEC);
        dup2(pdw[0], STDIN_FILENO);
        close(pdw[0]);
        fw = fdopen(pdw[1], fmw);
        if (fw) setbuf(fw, NULL);
    }

    if (fr || fw) {
        pid_child = spawnlp(P_NOWAIT, sh, sh, shopt, cmd, (char *)NULL);
    }
    if (pid_child == -1) {
        if (fr) { fclose(fr); fr = NULL; }
        if (fw) { fclose(fw); fw = NULL; }
    }
    else {
        if (fr) fr->_pid = pid_child;
        if (fw) fw->_pid = pid_child;
    }
    
    prev_errno = errno;
    if (ph_stdin != -1) {
        dup2(ph_stdin, STDIN_FILENO);
        close(ph_stdin);
        if (pm_stdin != -1) fcntl(STDIN_FILENO, F_SETFD, pm_stdin);
    }
    if (ph_stdout != -1) {
        dup2(ph_stdout, STDOUT_FILENO);
        close(ph_stdout);
        if (pm_stdout != -1) fcntl(STDOUT_FILENO, F_SETFD, pm_stdout);
    }
    errno = prev_errno;
    
    /* このへんで ATOMIC 解除? */
    
    if (fprd) *fprd = fr;
    if (fpwr) *fpwr = fw;
    return pid_child;
}


int
emx_duplex_pclose(FILE *fr, FILE *fw)
{
    FILE  *f = fr;
    
    if (fr && fw) {
        if (fr->_pid != fw->_pid) {
            errno = EBADF;
            return -1;
        }
        f = fr;
        if (fclose(fw) == -1) return -1;
    }
    else if (fr != NULL) f = fr;
    else if (fw != NULL) f = fw;
    else {
        errno = EBADF;
        return -1;
    }
    
    return pclose(f);
}


#if defined(TEST)

int main(int argc, char *argv[])
{
    int i, n;
    char *s;
    char buf[1024];
    FILE *fr = NULL, *fw = NULL;
    if (argc <= 1) {
        fprintf(stderr, "dupopen cmd args...\n");
        return 1;
    }
    
    n = 0;
    for(i=1; i<argc; i++) {
        n += strlen(argv[i]) + 1;
    }
    s = (char *)malloc(n);
    *s = '\0';
    for(i=1; i<argc; i++) {
        strcat(s, argv[i]);
        strcat(s, " ");
    }
    fprintf(stderr, "dupopen... %s\n", s);
    if (emx_duplex_popen(s, "r+", &fr, &fw) == -1) {
        fprintf(stderr, "err.\n");
        return 1;
    }
    
    fprintf(stderr, "put\n");
    fprintf(fw, "AAAAA\n");
    fprintf(stderr, "get\n");
    fgets(buf, sizeof(buf), fr);
    printf("%s\n", buf);
    
    emx_duplex_pclose(fr, fw);
    return 0;
}
#endif

エラーチェックが微妙に甘めか?(pipe 作成のあたりとか)

(2006-01-19) emx の libc だとリードモードの pclose は waitpid してから fclose しているが、fclose してから waitpid のほうがいいかもしれない。 (APUE に載ってる実装――プログラム 14.5 だとリードの場合もライトの場合も fclose → waitpid の順番になってる) というか順番変えたら ruby の test-all 時に openssl のとこではまらなくなった。うひー。

(2006-01-19 その2) あんまり関係ないような気もする。 というかやはりはまった。ひぎぃ。