qse/qse/lib/cmn/pio.c

824 lines
16 KiB
C

/*
* $Id: pio.c 193 2009-06-08 13:09:01Z hyunghwan.chung $
*
Copyright 2006-2009 Chung, Hyung-Hwan.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <qse/cmn/pio.h>
#include <qse/cmn/str.h>
#include "mem.h"
#ifdef _WIN32
# include <windows.h>
# include <tchar.h>
#else
# include "syscall.h"
# include <fcntl.h>
# include <sys/wait.h>
#endif
QSE_IMPLEMENT_COMMON_FUNCTIONS (pio)
static qse_ssize_t pio_input (int cmd, void* arg, void* buf, qse_size_t size);
static qse_ssize_t pio_output (int cmd, void* arg, void* buf, qse_size_t size);
qse_pio_t* qse_pio_open (
qse_mmgr_t* mmgr, qse_size_t ext,
const qse_char_t* path, int flags)
{
qse_pio_t* pio;
if (mmgr == QSE_NULL)
{
mmgr = QSE_MMGR_GETDFL();
QSE_ASSERTX (mmgr != QSE_NULL,
"Set the memory manager with QSE_MMGR_SETDFL()");
if (mmgr == QSE_NULL) return QSE_NULL;
}
pio = QSE_MMGR_ALLOC (mmgr, QSE_SIZEOF(qse_pio_t) + ext);
if (pio == QSE_NULL) return QSE_NULL;
if (qse_pio_init (pio, mmgr, path, flags) == QSE_NULL)
{
QSE_MMGR_FREE (mmgr, pio);
return QSE_NULL;
}
return pio;
}
void qse_pio_close (qse_pio_t* pio)
{
qse_pio_fini (pio);
QSE_MMGR_FREE (pio->mmgr, pio);
}
qse_pio_t* qse_pio_init (
qse_pio_t* pio, qse_mmgr_t* mmgr, const qse_char_t* cmd, int flags)
{
qse_pio_pid_t pid;
qse_pio_hnd_t handle[6] =
{
QSE_PIO_HND_NIL,
QSE_PIO_HND_NIL,
QSE_PIO_HND_NIL,
QSE_PIO_HND_NIL,
QSE_PIO_HND_NIL,
QSE_PIO_HND_NIL
};
qse_tio_t* tio[3] =
{
QSE_NULL,
QSE_NULL,
QSE_NULL
};
int i, minidx = -1, maxidx = -1;
#ifdef _WIN32
SECURITY_ATTRIBUTES secattr;
#endif
QSE_MEMSET (pio, 0, QSE_SIZEOF(*pio));
pio->mmgr = mmgr;
#ifdef _WIN32
/* http://msdn.microsoft.com/en-us/library/ms682499(VS.85).aspx */
secattr.nLength = QSE_SIZEOF(secattr);
secattr.bInheritHandle = TRUE;
secattr.lpSecurityDescriptor = NULL;
if (flags & QSE_PIO_WRITEIN)
{
if (CreatePipe (
&handle[0], &handle[1],
&secattr, 0) == FALSE) goto oops;
if (SetHandleInformation (
handle[0], HANDLE_FLAG_INHERIT, 0) == FALSE) goto oops;
minidx = 0; maxidx = 1;
}
if (flags & QSE_PIO_READOUT)
{
if (CreatePipe (
&handle[2], &handle[3],
&secattr, 0) == FALSE) goto oops;
if (SetHandleInformation (
handle[3], HANDLE_FLAG_INHERIT, 0) == FALSE) goto oops;
if (minidx == -1) minidx = 2;
maxidx = 3;
}
if (flags & QSE_PIO_READERR)
{
if (CreatePipe (
&handle[4], &handle[5],
&secattr, 0) == FALSE) goto oops;
if (SetHandleInformation (
handle[5], HANDLE_FLAG_INHERIT, 0) == FALSE) goto oops;
if (minidx == -1) minidx = 4;
maxidx = 5;
}
/* TODO: .... */
#else
if (flags & QSE_PIO_WRITEIN)
{
if (QSE_PIPE(&handle[0]) == -1) goto oops;
minidx = 0; maxidx = 1;
}
if (flags & QSE_PIO_READOUT)
{
if (QSE_PIPE(&handle[2]) == -1) goto oops;
if (minidx == -1) minidx = 2;
maxidx = 3;
}
if (flags & QSE_PIO_READERR)
{
if (QSE_PIPE(&handle[4]) == -1) goto oops;
if (minidx == -1) minidx = 4;
maxidx = 5;
}
if (maxidx == -1) goto oops;
pid = QSE_FORK();
if (pid == -1) goto oops;
if (pid == 0)
{
/* child */
qse_pio_hnd_t devnull;
qse_mchar_t* mcmd;
extern char** environ;
int fcnt = 0;
#ifndef QSE_CHAR_IS_MCHAR
qse_size_t n, mn, wl;
qse_char_t* wcmd = QSE_NULL;
qse_mchar_t buf[64];
#endif
if (flags & QSE_PIO_WRITEIN)
{
/* child should read */
QSE_CLOSE (handle[1]);
if (QSE_DUP2 (handle[0], 0) == -1) goto child_oops;
QSE_CLOSE (handle[0]);
}
if (flags & QSE_PIO_READOUT)
{
/* child should write */
QSE_CLOSE (handle[2]);
if (QSE_DUP2 (handle[3], 1) == -1) goto child_oops;
if (flags & QSE_PIO_ERRTOOUT)
{
if (QSE_DUP2 (handle[3], 2) == -1) goto child_oops;
}
QSE_CLOSE (handle[3]);
}
if (flags & QSE_PIO_READERR)
{
/* child should write */
QSE_CLOSE (handle[4]);
if (QSE_DUP2 (handle[5], 2) == -1) goto child_oops;
if (flags & QSE_PIO_OUTTOERR)
{
if (QSE_DUP2 (handle[5], 1) == -1) goto child_oops;
}
QSE_CLOSE (handle[5]);
}
if ((flags & QSE_PIO_INTONUL) ||
(flags & QSE_PIO_OUTTONUL) ||
(flags & QSE_PIO_ERRTONUL))
{
#ifdef O_LARGEFILE
devnull = QSE_OPEN ("/dev/null", O_RDWR|O_LARGEFILE, 0);
#else
devnull = QSE_OPEN ("/dev/null", O_RDWR, 0);
#endif
if (devnull == -1) goto oops;
}
if ((flags & QSE_PIO_INTONUL) &&
QSE_DUP2(devnull,0) == -1) goto child_oops;
if ((flags & QSE_PIO_OUTTONUL) &&
QSE_DUP2(devnull,1) == -1) goto child_oops;
if ((flags & QSE_PIO_ERRTONUL) &&
QSE_DUP2(devnull,2) == -1) goto child_oops;
if ((flags & QSE_PIO_INTONUL) ||
(flags & QSE_PIO_OUTTONUL) ||
(flags & QSE_PIO_ERRTONUL)) QSE_CLOSE (devnull);
if (flags & QSE_PIO_DROPIN) QSE_CLOSE(0);
if (flags & QSE_PIO_DROPOUT) QSE_CLOSE(1);
if (flags & QSE_PIO_DROPERR) QSE_CLOSE(2);
#ifdef QSE_CHAR_IS_MCHAR
if (flags & QSE_PIO_SHELL) mcmd = (qse_char_t*)cmd;
else
{
mcmd = qse_strdup (cmd, pio->mmgr);
if (mcmd == QSE_NULL) goto child_oops;
fcnt = qse_strspl (mcmd, QSE_T(""),
QSE_T('\"'), QSE_T('\"'), QSE_T('\''));
if (fcnt <= 0)
{
/* no field or an error */
goto child_oops;
}
}
#else
if (flags & QSE_PIO_SHELL)
{
n = qse_wcstombslen (cmd, &mn);
if (cmd[n] != QSE_WT('\0'))
{
/* cmd has illegal sequence */
goto child_oops;
}
}
else
{
wcmd = qse_strdup (cmd, pio->mmgr);
if (wcmd == QSE_NULL) goto child_oops;
fcnt = qse_strspl (wcmd, QSE_T(""),
QSE_T('\"'), QSE_T('\"'), QSE_T('\''));
if (fcnt <= 0)
{
/* no field or an error */
goto child_oops;
}
for (wl = 0, n = fcnt; n > 0; )
{
if (wcmd[wl++] == QSE_T('\0')) n--;
}
n = qse_wcsntombsnlen (wcmd, wl, &mn);
if (n != wl) goto child_oops;
}
mn = mn + 1;
if (mn <= QSE_COUNTOF(buf))
{
mcmd = buf;
mn = QSE_COUNTOF(buf);
}
else
{
mcmd = QSE_MMGR_ALLOC (
pio->mmgr, mn*QSE_SIZEOF(*mcmd));
if (mcmd == QSE_NULL) goto child_oops;
}
if (flags & QSE_PIO_SHELL)
{
/* qse_wcstombs() should succeed as
* qse_wcstombslen() was successful above */
qse_wcstombs (cmd, mcmd, &mn);
/* qse_wcstombs() null-terminate mcmd */
}
else
{
QSE_ASSERT (wcmd != QSE_NULL);
/* qse_wcsntombsn() should succeed as
* qse_wcsntombsnlen() was successful above */
qse_wcsntombsn (wcmd, wl, mcmd, &mn);
/* qse_wcsntombsn() doesn't null-terminate mcmd */
mcmd[mn] = QSE_MT('\0');
}
#endif
if (flags & QSE_PIO_SHELL)
{
const qse_mchar_t* argv[4];
argv[0] = QSE_MT("/bin/sh");
argv[1] = QSE_MT("-c");
argv[2] = mcmd;
argv[3] = QSE_NULL;
QSE_EXECVE (QSE_MT("/bin/sh"), argv, environ);
}
else
{
int i;
qse_mchar_t** argv;
argv = QSE_MMGR_ALLOC (pio->mmgr, (fcnt+1)*QSE_SIZEOF(argv[0]));
if (argv == QSE_NULL) goto child_oops;
for (i = 0; i < fcnt; i++)
{
argv[i] = mcmd;
while (*mcmd != QSE_MT('\0')) mcmd++;
mcmd++;
}
argv[i] = QSE_NULL;
QSE_EXECVE (argv[0], argv, environ);
}
child_oops:
QSE_EXIT (128);
}
/* parent */
pio->child = pid;
if (flags & QSE_PIO_WRITEIN)
{
/*
* 012345
* rw----
* X
* WRITE => 1
*/
QSE_CLOSE (handle[0]); handle[0] = QSE_PIO_HND_NIL;
}
if (flags & QSE_PIO_READOUT)
{
/*
* 012345
* --rw--
* X
* READ => 2
*/
QSE_CLOSE (handle[3]); handle[3] = QSE_PIO_HND_NIL;
}
if (flags & QSE_PIO_READERR)
{
/*
* 012345
* ----rw
* X
* READ => 4
*/
QSE_CLOSE (handle[5]); handle[5] = QSE_PIO_HND_NIL;
}
#endif
/* store back references */
pio->pin[QSE_PIO_IN].self = pio;
pio->pin[QSE_PIO_OUT].self = pio;
pio->pin[QSE_PIO_ERR].self = pio;
/* store actual pipe handles */
pio->pin[QSE_PIO_IN].handle = handle[1];
pio->pin[QSE_PIO_OUT].handle = handle[2];
pio->pin[QSE_PIO_ERR].handle = handle[4];
if (flags & QSE_PIO_TEXT)
{
for (i = 0; i < QSE_COUNTOF(tio); i++)
{
int r;
tio[i] = qse_tio_open (pio->mmgr, 0);
if (tio[i] == QSE_NULL) goto oops;
r = (i == QSE_PIO_IN)?
qse_tio_attachout (tio[i], pio_output, &pio->pin[i]):
qse_tio_attachin (tio[i], pio_input, &pio->pin[i]);
if (r == -1) goto oops;
pio->pin[i].tio = tio[i];
}
}
pio->flags = 0;
return pio;
oops:
for (i = 0; i < QSE_COUNTOF(tio); i++) qse_tio_close (tio[i]);
#if _WIN32
for (i = minidx; i < maxidx; i++) CloseHandle (handle[i]);
#else
for (i = minidx; i < maxidx; i++) QSE_CLOSE (handle[i]);
#endif
return QSE_NULL;
}
void qse_pio_fini (qse_pio_t* pio)
{
qse_pio_end (pio, QSE_PIO_IN);
qse_pio_end (pio, QSE_PIO_OUT);
qse_pio_end (pio, QSE_PIO_ERR);
qse_pio_setflags (pio, QSE_PIO_WAIT_NOBLOCK|QSE_PIO_WAIT_NORETRY, -1);
qse_pio_wait (pio);
}
int qse_pio_getflags (qse_pio_t* pio)
{
return pio->flags;
}
void qse_pio_setflags (qse_pio_t* pio, int flags, int op)
{
/*
op => set
op => off
op => on
*/
if (op == 0) pio->flags = flags;
else if (op > 0) pio->flags |= flags;
else /*if (op < 0)*/ pio->flags &= ~flags;
}
qse_pio_errnum_t qse_pio_geterrnum (qse_pio_t* pio)
{
return pio->errnum;
}
/* TODO: qse_pio_geterrmsg (qse_pio_t* pio) */
const qse_char_t* qse_pio_geterrstr (qse_pio_t* pio)
{
static const qse_char_t* __errstr[] =
{
QSE_T("no error"),
QSE_T("out of memory"),
QSE_T("no handle available"),
QSE_T("child process not valid"),
QSE_T("interruped"),
QSE_T("systeam call error"),
QSE_T("unknown error")
};
return __errstr[
(pio->errnum < 0 || pio->errnum >= QSE_COUNTOF(__errstr))?
QSE_COUNTOF(__errstr) - 1: pio->errnum];
}
qse_pio_hnd_t qse_pio_gethandle (qse_pio_t* pio, qse_pio_hid_t hid)
{
return pio->pin[hid].handle;
}
qse_pio_pid_t qse_pio_getchild (qse_pio_t* pio)
{
return pio->child;
}
static qse_ssize_t pio_read (
qse_pio_t* pio, void* buf, qse_size_t size, qse_pio_hnd_t hnd)
{
#ifdef _WIN32
DWORD count;
#else
qse_ssize_t n;
#endif
if (hnd == QSE_PIO_HND_NIL)
{
/* the stream is already closed */
pio->errnum = QSE_PIO_ENOHND;
return (qse_ssize_t)-1;
}
#ifdef _WIN32
if (size > QSE_TYPE_MAX(DWORD)) size = QSE_TYPE_MAX(DWORD);
if (ReadFile(hnd, buf, size, &count, QSE_NULL) == FALSE) return -1;
return (qse_ssize_t)count;
#else
if (size > QSE_TYPE_MAX(size_t)) size = QSE_TYPE_MAX(size_t);
reread:
n = QSE_READ (hnd, buf, size);
if (n == -1)
{
if (errno == EINTR)
{
if (pio->flags & QSE_PIO_READ_NORETRY)
pio->errnum = QSE_PIO_EINTR;
else goto reread;
}
else
{
pio->errnum = QSE_PIO_ESUBSYS;
}
}
return n;
#endif
}
qse_ssize_t qse_pio_read (
qse_pio_t* pio, void* buf, qse_size_t size, qse_pio_hid_t hid)
{
if (pio->pin[hid].tio == QSE_NULL)
return pio_read (pio, buf, size, pio->pin[hid].handle);
else
return qse_tio_read (pio->pin[hid].tio, buf, size);
}
static qse_ssize_t pio_write (
qse_pio_t* pio, const void* data, qse_size_t size, qse_pio_hnd_t hnd)
{
#ifdef _WIN32
DWORD count;
#else
qse_ssize_t n;
#endif
if (hnd == QSE_PIO_HND_NIL)
{
/* the stream is already closed */
pio->errnum = QSE_PIO_ENOHND;
return (qse_ssize_t)-1;
}
#ifdef _WIN32
if (size > QSE_TYPE_MAX(DWORD)) size = QSE_TYPE_MAX(DWORD);
if (WriteFile (hnd, data, size, &count, QSE_NULL) == FALSE) return -1;
return (qse_ssize_t)count;
#else
if (size > QSE_TYPE_MAX(size_t)) size = QSE_TYPE_MAX(size_t);
rewrite:
n = QSE_WRITE (hnd, data, size);
if (n == -1)
{
if (errno == EINTR)
{
if (pio->flags & QSE_PIO_WRITE_NORETRY)
pio->errnum = QSE_PIO_EINTR;
else goto rewrite;
}
else
{
pio->errnum = QSE_PIO_ESUBSYS;
}
}
return n;
#endif
}
qse_ssize_t qse_pio_write (
qse_pio_t* pio, const void* data, qse_size_t size,
qse_pio_hid_t hid)
{
if (pio->pin[hid].tio == QSE_NULL)
return pio_write (pio, data, size, pio->pin[hid].handle);
else
return qse_tio_write (pio->pin[hid].tio, data, size);
}
qse_ssize_t qse_pio_flush (qse_pio_t* pio, qse_pio_hid_t hid)
{
if (pio->pin[hid].tio == QSE_NULL) return 0;
return qse_tio_flush (pio->pin[hid].tio);
}
void qse_pio_end (qse_pio_t* pio, qse_pio_hid_t hid)
{
if (pio->pin[hid].tio != QSE_NULL)
{
qse_tio_close (pio->pin[hid].tio);
pio->pin[hid].tio = QSE_NULL;
}
if (pio->pin[hid].handle != QSE_PIO_HND_NIL)
{
#ifdef _WIN32
CloseHandle (pio->pin[hid].handle);
#else
QSE_CLOSE (pio->pin[hid].handle);
#endif
pio->pin[hid].handle = QSE_PIO_HND_NIL;
}
}
int qse_pio_wait (qse_pio_t* pio)
{
#ifdef _WIN32
DWORD ecode, w;
if (pio->child == QSE_PIO_PID_NIL)
{
pio->errnum = QSE_PIO_ECHILD;
return -1;
}
w = WaitForSingleObject (pio->child,
((pio->flags & QSE_PIO_WAIT_NOBLOCK)? 0: INFINITE)
);
if (w == WAIT_TIMEOUT)
{
/* the child process is still alive */
return 255 + 1;
}
if (w != WAIT_OBJECT_0)
{
/* WAIT_FAILED, WAIT_ABANDONED */
pio->errnum = QSE_PIO_ESUBSYS;
return -1;
}
QSE_ASSERT (w == WAIT_OBJECT_0);
if (GetExitCodeProcess (pio->child, &ecode) == FALSE)
{
/* close the handle anyway to prevent further
* errors when this function is called again */
CloseHandle (pio->child);
pio->child = QSE_PIO_PID_NIL;
pio->errnum = QSE_PIO_ESUBSYS;
return -1;
}
/* close handle here to emulate waitpid() as much as possible. */
CloseHandle (pio->child);
pio->child = QSE_PIO_PID_NIL;
if (ecode == STILL_ACTIVE)
{
/* this should not happen as the control reaches here
* only when WaitforSingleObject() is successful.
* if it happends, close the handle and return an error */
pio->errnum = QSE_PIO_ESUBSYS;
return -1;
}
return ecode;
#else
int opt = 0;
int ret = -1;
if (pio->child == QSE_PIO_PID_NIL)
{
pio->errnum = QSE_PIO_ECHILD;
return -1;
}
if (pio->flags & QSE_PIO_WAIT_NOBLOCK) opt |= WNOHANG;
while (1)
{
int status, n;
n = QSE_WAITPID (pio->child, &status, opt);
if (n == -1)
{
if (errno == ECHILD)
{
/* most likely, the process has already been
* waitpid()ed on. */
pio->child = QSE_PIO_PID_NIL;
pio->errnum = QSE_PIO_ECHILD;
}
else if (errno == EINTR)
{
if (pio->flags & QSE_PIO_WAIT_NORETRY)
pio->errnum = QSE_PIO_EINTR;
else continue;
}
else pio->errnum = QSE_PIO_ESUBSYS;
break;
}
if (n == 0)
{
/* when WNOHANG is not specified, 0 can't be returned */
QSE_ASSERT (pio->flags & QSE_PIO_WAIT_NOBLOCK);
ret = 255 + 1;
/* the child process is still alive */
break;
}
if (n == pio->child)
{
if (WIFEXITED(status))
{
/* the child process ended normally */
ret = WEXITSTATUS(status);
}
else if (WIFSIGNALED(status))
{
/* the child process was killed by a signal */
ret = 255 + 1 + WTERMSIG (status);
}
else
{
/* not interested in WIFSTOPPED & WIFCONTINUED.
* in fact, this else-block should not be reached
* as WIFEXITED or WIFSIGNALED must be true.
* anyhow, just set the return value to 0. */
ret = 0;
}
pio->child = QSE_PIO_PID_NIL;
break;
}
}
return ret;
#endif
}
int qse_pio_kill (qse_pio_t* pio)
{
#ifdef _WIN32
DWORD n;
#else
int n;
#endif
if (pio->child == QSE_PIO_PID_NIL)
{
pio->errnum = QSE_PIO_ECHILD;
return -1;
}
#ifdef _WIN32
/* 9 was chosen below to treat TerminateProcess as kill -KILL. */
n = TerminateProcess (pio->child, 255 + 1 + 9);
if (n == FALSE)
{
pio->errnum = QSE_PIO_ESUBSYS;
return -1;
}
return 0;
#else
n = QSE_KILL (pio->child, SIGKILL);
if (n == -1) pio->errnum = QSE_PIO_ESUBSYS;
return n;
#endif
}
static qse_ssize_t pio_input (int cmd, void* arg, void* buf, qse_size_t size)
{
qse_pio_pin_t* pin = (qse_pio_pin_t*)arg;
QSE_ASSERT (pin != QSE_NULL);
if (cmd == QSE_TIO_IO_DATA)
{
QSE_ASSERT (pin->self != QSE_NULL);
return pio_read (pin->self, buf, size, pin->handle);
}
/* take no actions for OPEN and CLOSE as they are handled
* by pio */
return 0;
}
static qse_ssize_t pio_output (int cmd, void* arg, void* buf, qse_size_t size)
{
qse_pio_pin_t* pin = (qse_pio_pin_t*)arg;
QSE_ASSERT (pin != QSE_NULL);
if (cmd == QSE_TIO_IO_DATA)
{
QSE_ASSERT (pin->self != QSE_NULL);
return pio_write (pin->self, buf, size, pin->handle);
}
/* take no actions for OPEN and CLOSE as they are handled
* by pio */
return 0;
}