diff --git a/qse/configure b/qse/configure index c139c368..b193d1be 100755 --- a/qse/configure +++ b/qse/configure @@ -16000,7 +16000,20 @@ fi done -for ac_header in time.h sys/time.h utime.h sys/resource.h sys/syscall.h sys/sendfile.h +for ac_header in time.h sys/time.h utime.h spawn.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + +for ac_header in sys/resource.h sys/syscall.h sys/sendfile.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" @@ -16164,6 +16177,17 @@ _ACEOF fi done +for ac_func in posix_spawn +do : + ac_fn_c_check_func "$LINENO" "posix_spawn" "ac_cv_func_posix_spawn" +if test "x$ac_cv_func_posix_spawn" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_POSIX_SPAWN 1 +_ACEOF + +fi +done + OLDLIBS="$LIBS" LIBS="$LIBM $LIBS" diff --git a/qse/configure.ac b/qse/configure.ac index cd90284a..bbb5b2ae 100644 --- a/qse/configure.ac +++ b/qse/configure.ac @@ -79,7 +79,8 @@ AC_SUBST(LIBM, $LIBM) dnl check header files. AC_HEADER_STDC AC_CHECK_HEADERS([stddef.h wchar.h wctype.h errno.h signal.h]) -AC_CHECK_HEADERS([time.h sys/time.h utime.h sys/resource.h sys/syscall.h sys/sendfile.h]) +AC_CHECK_HEADERS([time.h sys/time.h utime.h spawn.h]) +AC_CHECK_HEADERS([sys/resource.h sys/syscall.h sys/sendfile.h]) AC_CHECK_HEADERS([execinfo.h]) dnl check data types @@ -101,6 +102,7 @@ AC_CHECK_FUNCS([utime utimes]) AC_CHECK_FUNCS([sysconf]) AC_CHECK_FUNCS([backtrace backtrace_symbols]) AC_CHECK_FUNCS([fdopendir]) +AC_CHECK_FUNCS([posix_spawn]) OLDLIBS="$LIBS" LIBS="$LIBM $LIBS" diff --git a/qse/include/qse/config.h.in b/qse/include/qse/config.h.in index 2dddf53d..1afb7774 100644 --- a/qse/include/qse/config.h.in +++ b/qse/include/qse/config.h.in @@ -154,6 +154,9 @@ /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_NDIR_H +/* Define to 1 if you have the `posix_spawn' function. */ +#undef HAVE_POSIX_SPAWN + /* Define to 1 if you have the `pow' function. */ #undef HAVE_POW @@ -205,6 +208,9 @@ /* Define it socklen_t typedef is in sys/socket.h. */ #undef HAVE_SOCKLEN_T +/* Define to 1 if you have the header file. */ +#undef HAVE_SPAWN_H + /* Define to 1 if you have the `sqrt' function. */ #undef HAVE_SQRT diff --git a/qse/include/qse/net/httpd.h b/qse/include/qse/net/httpd.h index 2a956ece..62951418 100644 --- a/qse/include/qse/net/httpd.h +++ b/qse/include/qse/net/httpd.h @@ -232,6 +232,14 @@ qse_httpd_task_t* qse_httpd_entaskcgi ( const qse_htre_t* req ); +qse_httpd_task_t* qse_httpd_entasknph ( + qse_httpd_t* httpd, + qse_httpd_client_t* client, + const qse_httpd_task_t* pred, + const qse_mchar_t* path, + const qse_htre_t* req +); + /* -------------------------------------------- */ void* qse_httpd_allocmem ( diff --git a/qse/lib/cmn/pio.c b/qse/lib/cmn/pio.c index bae11128..ab5b9232 100644 --- a/qse/lib/cmn/pio.c +++ b/qse/lib/cmn/pio.c @@ -36,6 +36,9 @@ # include "syscall.h" # include # include +# if defined(HAVE_SPAWN_H) +# include +# endif #endif QSE_IMPLEMENT_COMMON_FUNCTIONS (pio) @@ -67,6 +70,254 @@ void qse_pio_close (qse_pio_t* pio) QSE_MMGR_FREE (pio->mmgr, pio); } +#if !defined(_WIN32) && !defined(__OS2__) && !defined(__DOS__) +struct param_t +{ + qse_mchar_t* mcmd; + qse_mchar_t* fixed_argv[4]; + qse_mchar_t** argv; + +#if defined(QSE_CHAR_IS_MCHAR) + /* nothign extra */ +#else + qse_mchar_t fixed_mbuf[64]; +#endif +}; +typedef struct param_t param_t; + +static void free_param (qse_pio_t* pio, param_t* param) +{ + if (param->argv && param->argv != param->fixed_argv) + QSE_MMGR_FREE (pio->mmgr, param->argv); + if (param->mcmd) QSE_MMGR_FREE (pio->mmgr, param->mcmd); +} + +static int make_param ( + qse_pio_t* pio, const qse_char_t* cmd, int flags, param_t* param) +{ +#if defined(QSE_CHAR_IS_MCHAR) + qse_mchar_t* mcmd = QSE_NULL; +#else + qse_mchar_t* mcmd = QSE_NULL; + qse_char_t* wcmd = QSE_NULL; +#endif + int fcnt = 0; + + QSE_MEMSET (param, 0, QSE_SIZEOF(*param)); + +#if defined(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) + { + pio->errnum = QSE_PIO_ENOMEM; + goto oops; + } + + fcnt = qse_strspl (mcmd, QSE_T(""), + QSE_T('\"'), QSE_T('\"'), QSE_T('\\')); + if (fcnt <= 0) + { + /* no field or an error */ + pio->errnum = QSE_PIO_EINVAL; + goto oops; + } + } +#else + if (flags & QSE_PIO_MBSCMD) + { + /* the cmd is flagged to be of qse_mchar_t + * while the default character type is qse_wchar_t. */ + + if (flags & QSE_PIO_SHELL) mcmd = (qse_mchar_t*)cmd; + else + { + mcmd = qse_mbsdup ((const qse_mchar_t*)cmd, pio->mmgr); + if (mcmd == QSE_NULL) + { + pio->errnum = QSE_PIO_ENOMEM; + goto oops; + } + + fcnt = qse_mbsspl (mcmd, QSE_MT(""), + QSE_MT('\"'), QSE_MT('\"'), QSE_MT('\\')); + if (fcnt <= 0) + { + /* no field or an error */ + pio->errnum = QSE_PIO_EINVAL; + goto oops; + } + } + } + else + { + qse_size_t n, mn, wl; + + if (flags & QSE_PIO_SHELL) + { + if (qse_wcstombs (cmd, &wl, QSE_NULL, &mn) <= -1) + { + /* cmd has illegal sequence */ + pio->errnum = QSE_PIO_EINVAL; + goto oops; + } + } + else + { + wcmd = qse_strdup (cmd, pio->mmgr); + if (wcmd == QSE_NULL) + { + pio->errnum = QSE_PIO_ENOMEM; + goto oops; + } + + fcnt = qse_strspl (wcmd, QSE_T(""), + QSE_T('\"'), QSE_T('\"'), QSE_T('\\')); + if (fcnt <= 0) + { + /* no field or an error */ + pio->errnum = QSE_PIO_EINVAL; + goto oops; + } + + /* calculate the length of the string after splitting */ + for (wl = 0, n = fcnt; n > 0; ) + { + if (wcmd[wl++] == QSE_T('\0')) n--; + } + + if (qse_wcsntombsn (wcmd, &wl, QSE_NULL, &mn) <= -1) + { + pio->errnum = QSE_PIO_EINVAL; + goto oops; + } + } + + /* prepare to reserve 1 more slot for the terminating '\0' + * by incrementing mn by 1. */ + mn = mn + 1; + + if (mn <= QSE_COUNTOF(param->fixed_mbuf)) + { + mcmd = param->fixed_mbuf; + mn = QSE_COUNTOF(param->fixed_mbuf); + } + else + { + mcmd = QSE_MMGR_ALLOC (pio->mmgr, mn * QSE_SIZEOF(*mcmd)); + if (mcmd == QSE_NULL) + { + pio->errnum = QSE_PIO_ENOMEM; + goto oops; + } + } + + if (flags & QSE_PIO_SHELL) + { + QSE_ASSERT (wcmd == QSE_NULL); + /* qse_wcstombs() should succeed as + * it was successful above */ + qse_wcstombs (cmd, &wl, mcmd, &mn); + /* qse_wcstombs() null-terminate mcmd */ + } + else + { + QSE_ASSERT (wcmd != QSE_NULL); + /* qse_wcsntombsn() should succeed as + * it was was successful above */ + qse_wcsntombsn (wcmd, &wl, mcmd, &mn); + /* qse_wcsntombsn() doesn't null-terminate mcmd */ + mcmd[mn] = QSE_MT('\0'); + + QSE_MMGR_FREE (pio->mmgr, wcmd); + wcmd = QSE_NULL; + } + } +#endif + + if (flags & QSE_PIO_SHELL) + { + param->argv = param->fixed_argv; + param->argv[0] = QSE_MT("/bin/sh"); + param->argv[1] = QSE_MT("-c"); + param->argv[2] = mcmd; + param->argv[3] = QSE_NULL; + } + else + { + int i; + qse_mchar_t** argv; + qse_mchar_t* mcmdptr; + + param->argv = QSE_MMGR_ALLOC ( + pio->mmgr, (fcnt + 1) * QSE_SIZEOF(argv[0])); + if (param->argv == QSE_NULL) + { + pio->errnum = QSE_PIO_ENOMEM; + goto oops; + } + + mcmdptr = mcmd; + for (i = 0; i < fcnt; i++) + { + param->argv[i] = mcmdptr; + while (*mcmdptr != QSE_MT('\0')) mcmdptr++; + mcmdptr++; + } + param->argv[i] = QSE_NULL; + } + +#if defined(QSE_CHAR_IS_MCHAR) + if (mcmd && mcmd != cmd) param->mcmd = mcmd; +#else + if (mcmd && mcmd != cmd && mcmd != param->fixed_mbuf) param->mcmd = mcmd; +#endif + return 0; + +oops: +#if defined(QSE_CHAR_IS_MCHAR) + if (mcmd && mcmd != cmd) QSE_MMGR_FREE (pio->mmgr, mcmd); +#else + if (mcmd && mcmd != cmd && mcmd != param->fixed_mbuf) QSE_MMGR_FREE (pio->mmgr, mcmd); + if (wcmd) QSE_MMGR_FREE (pio->mmgr, wcmd); +#endif + return -1; +} + +static QSE_INLINE int is_fd_valid (int fd) +{ + return fcntl (fd, F_GETFL) != -1 || errno != EBADF; +} + +static int get_highest_fd (void) +{ + /* TODO: consider if reading from /proc/self/fd is + * a better idea. */ + struct rlimit rlim; + int fd = -1; + +#if defined(F_MAXFD) + fd = fcntl (0, F_MAXFD); +#endif + if (fd == -1) + { + if (QSE_GETRLIMIT (RLIMIT_NOFILE, &rlim) <= -1 || + rlim.rlim_max == RLIM_INFINITY) + { + #if defined(HAVE_SYSCONF) + fd = sysconf (_SC_OPEN_MAX); + #endif + } + else fd = rlim.rlim_max; + } + if (fd == -1) fd = 1024; /* fallback */ + return fd; +} + +#endif + int qse_pio_init ( qse_pio_t* pio, qse_mmgr_t* mmgr, const qse_char_t* cmd, qse_env_t* env, int flags) @@ -113,8 +364,17 @@ int qse_pio_init ( /* DOS not multi-processed. can't support pio */ +#elif defined(HAVE_POSIX_SPAWN) + posix_spawn_file_actions_t fa; + int fa_inited = 0; + int spawn_ret; + qse_pio_pid_t pid; + param_t param; + extern char** environ; #else qse_pio_pid_t pid; + param_t param; + extern char** environ; #endif QSE_MEMSET (pio, 0, QSE_SIZEOF(*pio)); @@ -618,6 +878,137 @@ int qse_pio_init ( /* DOS not multi-processed. can't support pio */ return -1; +#elif defined(HAVE_POSIX_SPAWN) + 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) + { + pio->errnum = QSE_PIO_EINVAL; + goto oops; + } + + if (posix_spawn_file_actions_init (&fa) != 0) goto oops; + fa_inited = 1; + + if (flags & QSE_PIO_WRITEIN) + { + /* child should read */ + if (posix_spawn_file_actions_addclose (&fa, handle[1]) != 0) goto oops; + if (posix_spawn_file_actions_adddup2 (&fa, handle[0], 0) != 0) goto oops; + if (posix_spawn_file_actions_addclose (&fa, handle[0]) != 0) goto oops; + } + + if (flags & QSE_PIO_READOUT) + { + /* child should write */ + if (posix_spawn_file_actions_addclose (&fa, handle[2]) != 0) goto oops; + if (posix_spawn_file_actions_adddup2 (&fa, handle[3], 1) != 0) goto oops; + if ((flags & QSE_PIO_ERRTOOUT) && + posix_spawn_file_actions_adddup2 (&fa, handle[3], 2) != 0) goto oops; + if (posix_spawn_file_actions_addclose (&fa, handle[3]) != 0) goto oops; + } + + if (flags & QSE_PIO_READERR) + { + /* child should write */ + if (posix_spawn_file_actions_addclose (&fa, handle[4]) != 0) goto oops; + if (posix_spawn_file_actions_adddup2 (&fa, handle[5], 2) != 0) goto oops; + if ((flags & QSE_PIO_OUTTOERR) && + posix_spawn_file_actions_adddup2 (&fa, handle[5], 1) != 0) goto oops; + if (posix_spawn_file_actions_addclose (&fa, handle[5]) != 0) goto oops; + } + + { + int oflags = O_RDWR; + #if defined(O_LARGEFILE) + oflags |= O_LARGEFILE; + #endif + + if ((flags & QSE_PIO_INTONUL) && + posix_spawn_file_actions_addopen (&fa, 0, QSE_MT("/dev/null"), oflags, 0) != 0) goto oops; + if ((flags & QSE_PIO_OUTTONUL) && + posix_spawn_file_actions_addopen (&fa, 1, QSE_MT("/dev/null"), oflags, 0) != 0) goto oops; + if ((flags & QSE_PIO_ERRTONUL) && + posix_spawn_file_actions_addopen (&fa, 2, QSE_MT("/dev/null"), oflags, 0) != 0) goto oops; + } + + /* there remains the chance of race condition that + * 0, 1, 2 can be closed between addclose() and posix_spawn(). + * so checking the file descriptors with is_fd_valid() is + * just on the best-effort basis. + */ + if ((flags & QSE_PIO_DROPIN) && is_fd_valid(0) && + posix_spawn_file_actions_addclose (&fa, 0) != 0) goto oops; + if ((flags & QSE_PIO_DROPOUT) && is_fd_valid(1) && + posix_spawn_file_actions_addclose (&fa, 1) != 0) goto oops; + if ((flags & QSE_PIO_DROPERR) && is_fd_valid(2) && + posix_spawn_file_actions_addclose (&fa, 2) != 0) goto oops; + + { + int fd = get_highest_fd (); + while (--fd > 2) + { + if (fd != handle[0] && + fd != handle[1] && + fd != handle[2] && + fd != handle[3] && + fd != handle[4] && + fd != handle[5]) + { + /* closing attempt on a best-effort basis */ + if (is_fd_valid(fd) && + posix_spawn_file_actions_addclose (&fa, fd) != 0) goto oops; + } + } + } + + if (make_param (pio, cmd, flags, ¶m) <= -1) goto oops; + spawn_ret = posix_spawn( + &pid, param.argv[0], &fa, QSE_NULL, param.argv, + (env? qse_env_getarr(env): environ)); + free_param (pio, ¶m); + if (fa_inited) + { + posix_spawn_file_actions_destroy (&fa); + fa_inited = 0; + } + if (spawn_ret != 0) goto oops; + + pio->child = pid; + if (flags & QSE_PIO_WRITEIN) + { + QSE_CLOSE (handle[0]); + handle[0] = QSE_PIO_HND_NIL; + } + if (flags & QSE_PIO_READOUT) + { + QSE_CLOSE (handle[3]); + handle[3] = QSE_PIO_HND_NIL; + } + if (flags & QSE_PIO_READERR) + { + QSE_CLOSE (handle[5]); + handle[5] = QSE_PIO_HND_NIL; + } + #else if (flags & QSE_PIO_WRITEIN) @@ -652,31 +1043,10 @@ int qse_pio_init ( if (pid == 0) { /* child */ + qse_pio_hnd_t devnull = -1; + int fd = get_highest_fd (); - qse_pio_hnd_t devnull; - qse_mchar_t* mcmd; - int fcnt = 0; - #ifndef QSE_CHAR_IS_MCHAR - qse_mchar_t buf[64]; - #endif - - /* TODO: consider if reading from /proc/self/fd is - * a better idea. */ - - struct rlimit rlim; - int fd; - - if (QSE_GETRLIMIT (RLIMIT_NOFILE, &rlim) <= -1 || - rlim.rlim_max == RLIM_INFINITY) - { - #ifdef HAVE_SYSCONF - fd = sysconf (_SC_OPEN_MAX); - if (fd <= 0) - #endif - fd = 1024; - } - else fd = rlim.rlim_max; - + /* don't close stdin/out/err and the pipes */ while (--fd > 2) { if (fd != handle[0] && @@ -733,7 +1103,7 @@ int qse_pio_init ( (flags & QSE_PIO_OUTTONUL) || (flags & QSE_PIO_ERRTONUL)) { - #ifdef O_LARGEFILE + #if defined(O_LARGEFILE) devnull = QSE_OPEN (QSE_MT("/dev/null"), O_RDWR|O_LARGEFILE, 0); #else devnull = QSE_OPEN (QSE_MT("/dev/null"), O_RDWR, 0); @@ -750,162 +1120,22 @@ int qse_pio_init ( if ((flags & QSE_PIO_INTONUL) || (flags & QSE_PIO_OUTTONUL) || - (flags & QSE_PIO_ERRTONUL)) QSE_CLOSE (devnull); + (flags & QSE_PIO_ERRTONUL)) + { + QSE_CLOSE (devnull); + devnull = -1; + } 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_MBSCMD) - { - /* the cmd is flagged to be of qse_mchar_t - * while the default character type is qse_wchar_t. */ - - if (flags & QSE_PIO_SHELL) mcmd = (qse_mchar_t*)cmd; - else - { - mcmd = qse_mbsdup ((const qse_mchar_t*)cmd, pio->mmgr); - if (mcmd == QSE_NULL) goto child_oops; - - fcnt = qse_mbsspl (mcmd, QSE_MT(""), - QSE_MT('\"'), QSE_MT('\"'), QSE_MT('\\')); - if (fcnt <= 0) - { - /* no field or an error */ - goto child_oops; - } - } - } - else - { - qse_size_t n, mn, wl; - qse_char_t* wcmd = QSE_NULL; - - if (flags & QSE_PIO_SHELL) - { - if (qse_wcstombs (cmd, &wl, QSE_NULL, &mn) <= -1) - { - /* 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; - } - - /* calculate the length of the string after splitting */ - for (wl = 0, n = fcnt; n > 0; ) - { - if (wcmd[wl++] == QSE_T('\0')) n--; - } - -#if 0 - n = qse_wcsntombsnlen (wcmd, wl, &mn); - if (n != wl) goto child_oops; -#endif - if (qse_wcsntombsn (wcmd, &wl, QSE_NULL, &mn) <= -1) goto child_oops; - } - - /* prepare to reserve 1 more slot for the terminating '\0' - * by incrementing mn by 1. */ - 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 - * it was successful above */ - qse_wcstombs (cmd, &wl, mcmd, &mn); - /* qse_wcstombs() null-terminate mcmd */ - } - else - { - QSE_ASSERT (wcmd != QSE_NULL); - /* qse_wcsntombsn() should succeed as - * it was 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]; - extern char** environ; - - argv[0] = QSE_MT("/bin/sh"); - argv[1] = QSE_MT("-c"); - argv[2] = mcmd; - argv[3] = QSE_NULL; - - QSE_EXECVE ( - QSE_MT("/bin/sh"), - (qse_mchar_t*const*)argv, - (env? qse_env_getarr(env): environ) - ); - } - else - { - int i; - qse_mchar_t** argv; - extern char** environ; - - 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, (env? qse_env_getarr(env): environ)); - - /* this won't be reached if execve succeeds */ - QSE_MMGR_FREE (pio->mmgr, argv); - } + if (make_param (pio, cmd, flags, ¶m) <= -1) goto child_oops; + QSE_EXECVE (param.argv[0], param.argv, (env? qse_env_getarr(env): environ)); + free_param (pio, ¶m); child_oops: + if (devnull >= 0) QSE_CLOSE (devnull); QSE_EXIT (128); } @@ -995,8 +1225,7 @@ int qse_pio_init ( return 0; oops: - if (pio->errnum == QSE_PIO_ENOERR) - pio->errnum = QSE_PIO_ESUBSYS; + if (pio->errnum == QSE_PIO_ENOERR) pio->errnum = QSE_PIO_ESUBSYS; #if defined(_WIN32) if (windevnul != INVALID_HANDLE_VALUE) CloseHandle (windevnul); @@ -1036,7 +1265,16 @@ oops: #elif defined(__DOS__) /* DOS not multi-processed. can't support pio */ - +#elif defined(HAVE_POSIX_SPAWN) + if (fa_inited) + { + posix_spawn_file_actions_destroy (&fa); + fa_inited = 0; + } + for (i = minidx; i < maxidx; i++) + { + if (handle[i] != QSE_PIO_HND_NIL) QSE_CLOSE (handle[i]); + } #else for (i = minidx; i < maxidx; i++) { diff --git a/qse/lib/net/httpd_task.c b/qse/lib/net/httpd_task.c index 4ad36b7c..06ee02f7 100644 --- a/qse/lib/net/httpd_task.c +++ b/qse/lib/net/httpd_task.c @@ -1210,6 +1210,7 @@ struct task_cgi_arg_t { const qse_mchar_t* path; const qse_htre_t* req; + int nph; }; typedef struct task_cgi_t task_cgi_t; @@ -1220,6 +1221,7 @@ struct task_cgi_t const qse_mchar_t* path; qse_http_version_t version; int keepalive; /* taken from the request */ + int nph; qse_env_t* env; qse_pio_t* pio; @@ -1254,7 +1256,7 @@ int walk_cgi_headers (qse_htre_t* req, const qse_mchar_t* key, const qse_mchar_t { task_cgi_t* cgi = (task_cgi_t*)ctx; - if (qse_mbscmp (key, "Status") != 0) + if (qse_mbscmp (key, QSE_MT("Status")) != 0) { if (qse_mbs_cat (cgi->res, key) == (qse_size_t)-1) return -1; if (qse_mbs_cat (cgi->res, QSE_MT(": ")) == (qse_size_t)-1) return -1; @@ -1275,6 +1277,7 @@ static int cgi_htrd_handle_request (qse_htrd_t* htrd, qse_htre_t* req) QSE_ASSERT (req->attr.hurried); status = qse_htre_getheaderval (req, QSE_MT("Status")); + if (status) { qse_mchar_t buf[128]; @@ -1282,8 +1285,7 @@ static int cgi_htrd_handle_request (qse_htrd_t* htrd, qse_htre_t* req) qse_mchar_t* endptr; /* TODO: check the syntax of status value??? */ - - QSE_MSTRTONUM (nstatus,status,&endptr,10); + QSE_MSTRTONUM (nstatus, status, &endptr, 10); snprintf (buf, QSE_COUNTOF(buf), QSE_MT("HTTP/%d.%d %d "), @@ -1302,14 +1304,31 @@ static int cgi_htrd_handle_request (qse_htrd_t* htrd, qse_htre_t* req) if (qse_mbs_cat (cgi->res, endptr) == (qse_size_t)-1) return -1; if (qse_mbs_cat (cgi->res, QSE_MT("\r\n")) == (qse_size_t)-1) return -1; } - else + else { + qse_mchar_t* location; qse_mchar_t buf[128]; - snprintf (buf, QSE_COUNTOF(buf), - QSE_MT("HTTP/%d.%d 200 OK\r\n"), - cgi->version.major, cgi->version.minor - ); - if (qse_mbs_cat (cgi->res, buf) == (qse_size_t)-1) return -1; + + location = qse_htre_getheaderval (req, QSE_MT("Location")); + if (location) + { + snprintf (buf, QSE_COUNTOF(buf), + QSE_MT("HTTP/%d.%d 301 Moved Permanently\r\n"), + cgi->version.major, cgi->version.minor + ); + if (qse_mbs_cat (cgi->res, buf) == (qse_size_t)-1) return -1; + + /* the actual Location hearer is added by + * qse_htre_walkheaders() below */ + } + else + { + snprintf (buf, QSE_COUNTOF(buf), + QSE_MT("HTTP/%d.%d 200 OK\r\n"), + cgi->version.major, cgi->version.minor + ); + if (qse_mbs_cat (cgi->res, buf) == (qse_size_t)-1) return -1; + } } if (req->attr.content_length_set) @@ -1319,20 +1338,29 @@ static int cgi_htrd_handle_request (qse_htrd_t* htrd, qse_htre_t* req) } else { - /* no Content-Length returned by CGI */ + /* no Content-Length returned by CGI. */ if (qse_comparehttpversions (&cgi->version, &v11) >= 0) + { cgi->content_chunked = 1; - else cgi->disconnect = 1; + } + else + { + /* If CGI doesn't specify Content-Length, i can't + * honor cgi->keepalive in HTTP/1.0 or earlier. + * Closing the connection is the only way to + * specify how long the content is */ + cgi->disconnect = 1; + } } - if (cgi->content_chunked) - { - if (qse_mbs_cat (cgi->res, QSE_MT("Transfer-Encoding: chunked\r\n")) == (qse_size_t)-1) return -1; - } + if (cgi->content_chunked && + qse_mbs_cat (cgi->res, QSE_MT("Transfer-Encoding: chunked\r\n")) == (qse_size_t)-1) return -1; if (qse_htre_walkheaders (req, walk_cgi_headers, cgi) <= -1) return -1; - if (qse_mbs_ncat (cgi->res, QSE_MT("\r\n"), 2) == (qse_size_t)-1) return -1; + /* end of header */ + if (qse_mbs_cat (cgi->res, QSE_MT("\r\n")) == (qse_size_t)-1) return -1; + /* content body begins here */ cgi->content_received = qse_htre_getcontentlen(req); if (cgi->content_length_set && cgi->content_received > cgi->content_length) @@ -1379,6 +1407,14 @@ static qse_env_t* makecgienv ( #ifdef _WIN32 qse_env_insertsys (env, QSE_T("PATH")); + + { + qse_char_t proto[32]; + qse_http_version_t* v = qse_htre_getversion(req); + snprintf (proto, QSE_COUNTOF(proto), + QSE_T("HTTP/%d.%d"), (int)v->major, (int)v->minor); + qse_env_insert (env, QSE_T("SERVER_PROTOCOL"), proto); + } #else qse_env_insertsysm (env, QSE_MT("LANG")); qse_env_insertsysm (env, QSE_MT("PATH")); @@ -1387,9 +1423,17 @@ static qse_env_t* makecgienv ( { qse_mchar_t port[16]; snprintf (port, QSE_COUNTOF(port), - "%d", (int)ntohs(client->addr.in4.sin_port)); + QSE_MT("%d"), (int)ntohs(client->addr.in4.sin_port)); qse_env_insertm (env, QSE_MT("REMOTE_PORT"), port); } + + { + qse_mchar_t proto[32]; + qse_http_version_t* v = qse_htre_getversion(req); + snprintf (proto, QSE_COUNTOF(proto), + QSE_MT("HTTP/%d.%d"), (int)v->major, (int)v->minor); + qse_env_insertm (env, QSE_MT("SERVER_PROTOCOL"), proto); + } //qse_env_insertm (env, QSE_MT("REMOTE_ADDR"), QSE_MT("what the hell")); #endif @@ -1418,6 +1462,7 @@ static int task_init_cgi ( xtn->path = (qse_mchar_t*)(xtn + 1); xtn->version = *qse_htre_getversion(arg->req); xtn->keepalive = arg->req->attr.keepalive; + xtn->nph = arg->nph; xtn->env = makecgienv (httpd, client, arg->req); if (xtn->env == QSE_NULL) xtn->init_failed = 1; @@ -1678,24 +1723,35 @@ static int task_main_cgi ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { task_cgi_t* cgi = (task_cgi_t*)task->ctx; - cgi_htrd_xtn_t* xtn; if (cgi->init_failed) goto oops; - cgi->htrd = qse_htrd_open (httpd->mmgr, QSE_SIZEOF(cgi_htrd_xtn_t)); - if (cgi->htrd == QSE_NULL) goto oops; - xtn = (cgi_htrd_xtn_t*) qse_htrd_getxtn (cgi->htrd); - xtn->cgi = cgi; - qse_htrd_setrecbs (cgi->htrd, &cgi_htrd_cbs); - qse_htrd_setoption ( - cgi->htrd, - QSE_HTRD_SKIPINITIALLINE | - QSE_HTRD_HURRIED | - QSE_HTRD_REQUEST - ); + if (cgi->nph) + { + /* i cannot know how long the content will be + * since i don't parse the header. so i have to close + * the connection regardless of content-length or transfer-encoding + * in the actual header. */ + if (qse_httpd_entaskdisconnect (httpd, client, task) == QSE_NULL) return -1; + } + else + { + cgi_htrd_xtn_t* xtn; + cgi->htrd = qse_htrd_open (httpd->mmgr, QSE_SIZEOF(cgi_htrd_xtn_t)); + if (cgi->htrd == QSE_NULL) goto oops; + xtn = (cgi_htrd_xtn_t*) qse_htrd_getxtn (cgi->htrd); + xtn->cgi = cgi; + qse_htrd_setrecbs (cgi->htrd, &cgi_htrd_cbs); + qse_htrd_setoption ( + cgi->htrd, + QSE_HTRD_SKIPINITIALLINE | + QSE_HTRD_HURRIED | + QSE_HTRD_REQUEST + ); - cgi->res = qse_mbs_open (httpd->mmgr, 0, 256); - if (cgi->res == QSE_NULL) goto oops; + cgi->res = qse_mbs_open (httpd->mmgr, 0, 256); + if (cgi->res == QSE_NULL) goto oops; + } cgi->pio = qse_pio_open ( httpd->mmgr, 0, (const qse_char_t*)cgi->path, cgi->env, @@ -1703,8 +1759,17 @@ static int task_main_cgi ( ); if (cgi->pio == QSE_NULL) goto oops; - task->main = task_main_cgi_2; /* cause this function to be called subsequently */ - return task_main_cgi_2 (httpd, client, task); /* let me call it here once */ + if (cgi->nph) + { + /* skip various header processing */ + task->main = task_main_cgi_4; + return task_main_cgi_4 (httpd, client, task); + } + else + { + task->main = task_main_cgi_2; /* cause this function to be called subsequently */ + return task_main_cgi_2 (httpd, client, task); /* let me call it here once */ + } oops: if (cgi->res) @@ -1721,6 +1786,10 @@ oops: return (entask_error (httpd, client, task, 500, &cgi->version, cgi->keepalive) == QSE_NULL)? -1: 0; } +/* TODO: global option or individual paramter for max cgi lifetime +* non-blocking pio read ... +*/ + qse_httpd_task_t* qse_httpd_entaskcgi ( qse_httpd_t* httpd, qse_httpd_client_t* client, @@ -1733,6 +1802,33 @@ qse_httpd_task_t* qse_httpd_entaskcgi ( arg.path = path; arg.req = req; + arg.nph = 0; + + QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); + task.init = task_init_cgi; + task.fini = task_fini_cgi; + task.main = task_main_cgi; + task.ctx = &arg; + + return qse_httpd_entask ( + httpd, client, pred, &task, + QSE_SIZEOF(task_cgi_t) + ((qse_mbslen(path) + 1) * QSE_SIZEOF(*path)) + ); +} + +qse_httpd_task_t* qse_httpd_entasknph ( + qse_httpd_t* httpd, + qse_httpd_client_t* client, + const qse_httpd_task_t* pred, + const qse_mchar_t* path, + const qse_htre_t* req) +{ + qse_httpd_task_t task; + task_cgi_arg_t arg; + + arg.path = path; + arg.req = req; + arg.nph = 1; QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); task.init = task_init_cgi; diff --git a/qse/samples/net/http01.c b/qse/samples/net/http01.c index 32ecf270..8d430c2b 100644 --- a/qse/samples/net/http01.c +++ b/qse/samples/net/http01.c @@ -69,12 +69,13 @@ qse_printf (QSE_T("content = [%.*S]\n"), x = qse_httpd_entaskcgi ( httpd, client, QSE_NULL, qpath, req); if (x == QSE_NULL) goto oops; - -#if 0 - x = qse_httpd_entasknphcgi ( + } + else if (dot && qse_mbscmp (dot, QSE_MT(".nph")) == 0) + { + /* nph-cgi */ + x = qse_httpd_entasknph ( httpd, client, QSE_NULL, qpath, req); if (x == QSE_NULL) goto oops; -#endif } else {