diff --git a/qse/cmd/http/httpd.c b/qse/cmd/http/httpd.c
index 9ce91cb9..ca79c6e5 100644
--- a/qse/cmd/http/httpd.c
+++ b/qse/cmd/http/httpd.c
@@ -357,6 +357,33 @@ static int daemonize (int devnull)
 
 /* --------------------------------------------------------------------- */
 
+int xxxx (void* ctx, qse_env_char_t** envir)
+{
+	extern char** environ;
+char buf[1000];
+char* cl;
+int cl_i = 0;
+	environ = envir;
+	printf ("Content-Type: text/html\n\n");
+	printf ("
\n");
+	system ("ls -laF /tmp");
+	printf ("--------------------\n");
+	system ("printenv");
+ cl = getenv("CONTENT_LENGTH");
+if (cl) cl_i = atoi(cl);
+//if (cl_i)
+//{
+while (fgets (buf, sizeof(buf), stdin) != NULL)
+{
+printf ("%s", buf);
+}
+//}
+//	system ("while read xxx; do echo $xxx; done; echo 123 456 789");
+	printf ("\n");
+
+	return 0;
+}
+
 static int make_resource (
 	qse_httpd_t* httpd, qse_httpd_client_t* client,
 	qse_htre_t* req, qse_httpd_rsrc_t* rsrc)
@@ -406,7 +433,7 @@ static loccfg_t* find_loccfg (
 		for (loccfg = hostcfg->loccfg; loccfg; loccfg = loccfg->next)
 		{
 			QSE_ASSERT (loccfg->locname.len > 0);
-				if (qse_mbsbeg (qpath, loccfg->locname.ptr) && 
+			if (qse_mbsbeg (qpath, loccfg->locname.ptr) && 
 			    (loccfg->locname.ptr[loccfg->locname.len - 1] == QSE_MT('/') ||
 			    qpath[loccfg->locname.len] == QSE_MT('/') || 
 			    qpath[loccfg->locname.len] == QSE_MT('\0'))) 
@@ -708,10 +735,10 @@ found:
 			if (qse_mbscmp (qse_htre_getqpath(qinfo->req), QSE_MT("/version")) == 0)
 			{
 				/* return static text without inspecting further */
-				((qse_httpd_serverstd_root_t*)result)->type = QSE_HTTPD_SERVERSTD_ROOT_TEXT;
+			
+				/*((qse_httpd_serverstd_root_t*)result)->type = QSE_HTTPD_SERVERSTD_ROOT_TEXT;
 				((qse_httpd_serverstd_root_t*)result)->u.text.ptr = QSE_MT(QSE_PACKAGE_NAME " " QSE_PACKAGE_VERSION);
-				((qse_httpd_serverstd_root_t*)result)->u.text.mime = QSE_MT("text/plain");
-			}
+				((qse_httpd_serverstd_root_t*)result)->u.text.mime = QSE_MT("text/plain");*/
 			else
 			#endif
 
@@ -724,7 +751,10 @@ found:
 
 			((qse_httpd_serverstd_realm_t*)result)->name = loccfg->xcfg[XCFG_REALM];
 
+			/* qinfo->xpath is not available for the REALM query in the std implementation.
+			 * let me check if it's available in case the implementation changes */
 			apath = qinfo->xpath? qinfo->xpath: qse_htre_getqpath (qinfo->req);
+printf ("apth in READM QUERY [%s]\n", apath);
 			if (apath)
 			{
 				const qse_mchar_t* base;
@@ -803,25 +833,44 @@ found:
 			qse_size_t i;
 			qse_httpd_serverstd_cgi_t* scgi;
 			const qse_mchar_t* xpath_base;
+			qse_mchar_t* qpath;
 
-			xpath_base = qse_mbsbasename (qinfo->xpath);
+			qpath = qse_htre_getqpath(qinfo->req);
 
 			scgi = (qse_httpd_serverstd_cgi_t*)result;
 			qse_memset (scgi, 0, QSE_SIZEOF(*scgi));
 
-			for (i = 0; i < QSE_COUNTOF(loccfg->cgi); i++)
+#if 1
+printf ("qinfo->xpath ####### [%s] %d [%s]\n", qinfo->xpath, qinfo->xpath_nx, qpath);
+			//if (qse_mbscmp (qinfo->xpath, QSE_MT("/tmp/version.cgi")) == 0)
+			if (qse_mbscmp (qpath, QSE_MT("/local/version.cgi")) == 0)
 			{
-				struct cgi_t* cgi;
-				for (cgi = loccfg->cgi[i].head; cgi; cgi = cgi->next)
+				scgi->cgi = 1;
+				scgi->nph = 0;
+				scgi->fncptr = xxxx;
+				scgi->shebang = QSE_NULL;
+				return 0;
+			}
+#endif
+
+
+			if (!qinfo->xpath_nx)
+			{
+				xpath_base = qse_mbsbasename (qinfo->xpath);
+				for (i = 0; i < QSE_COUNTOF(loccfg->cgi); i++)
 				{
-					if ((cgi->type == CGI_PREFIX && qse_mbsbeg (xpath_base, cgi->spec)) ||
-					    (cgi->type == CGI_SUFFIX && qse_mbsend (xpath_base, cgi->spec)) ||
-					    (cgi->type == CGI_NAME && qse_mbscmp (xpath_base, cgi->spec) == 0))
+					struct cgi_t* cgi;
+					for (cgi = loccfg->cgi[i].head; cgi; cgi = cgi->next)
 					{
-						scgi->cgi = 1;
-						scgi->nph = cgi->nph;
-						scgi->shebang = cgi->shebang;
-						return 0;
+						if ((cgi->type == CGI_PREFIX && qse_mbsbeg (xpath_base, cgi->spec)) ||
+						    (cgi->type == CGI_SUFFIX && qse_mbsend (xpath_base, cgi->spec)) ||
+						    (cgi->type == CGI_NAME && qse_mbscmp (xpath_base, cgi->spec) == 0))
+						{
+							scgi->cgi = 1;
+							scgi->nph = cgi->nph;
+							scgi->shebang = cgi->shebang;
+							return 0;
+						}
 					}
 				}
 			}
@@ -874,7 +923,7 @@ found:
 					id = (code == QSE_HTTPD_SERVERSTD_DIRACC)? 0: 1;
 		
 					xpath_base = qse_mbsbasename (qinfo->xpath);
-		
+
 					*(int*)result = 200;
 					for (i = 0; i < QSE_COUNTOF(loccfg->access[id]); i++)
 					{
diff --git a/qse/include/qse/cmn/pio.h b/qse/include/qse/cmn/pio.h
index 4f845c3d..77e0a8fc 100644
--- a/qse/include/qse/cmn/pio.h
+++ b/qse/include/qse/cmn/pio.h
@@ -59,6 +59,10 @@ enum qse_pio_flag_t
 	 *  them to prevent inheritance. */
 	QSE_PIO_NOCLOEXEC     = (1 << 5),
 
+	/** indidate that the command to qse_pio_open() is a pointer to
+	 *  #qse_pio_fnc_t. supported on unix/linux only */
+	QSE_PIO_FNCCMD        = (1 << 6),
+
 	/** write to stdin of a child process */
 	QSE_PIO_WRITEIN       = (1 << 8),
 	/** read stdout of a child process */
@@ -113,6 +117,16 @@ enum qse_pio_hid_t
 };
 typedef enum qse_pio_hid_t qse_pio_hid_t;
 
+
+typedef int (*qse_pio_fncptr_t) (void* ctx, qse_env_char_t** envir);
+
+typedef struct qse_pio_fnc_t qse_pio_fnc_t;
+struct qse_pio_fnc_t
+{
+	qse_pio_fncptr_t ptr;
+	void* ctx;
+};
+
 /**
  * The qse_pio_errnum_t type defines error numbers.
  */
@@ -177,6 +191,7 @@ struct qse_pio_pin_t
 	qse_pio_t*    self;	
 };
 
+
 /**
  * The qse_pio_t type defines a structure to store status for piped I/O
  * to a child process. The qse_pio_xxx() funtions are written around this
diff --git a/qse/include/qse/http/httpd.h b/qse/include/qse/http/httpd.h
index 431a9e12..1b3a1906 100644
--- a/qse/include/qse/http/httpd.h
+++ b/qse/include/qse/http/httpd.h
@@ -28,6 +28,8 @@
 #include 
 #include 
 #include 
+#include 
+
 
 typedef struct qse_httpd_t        qse_httpd_t;
 typedef struct qse_httpd_mate_t   qse_httpd_mate_t;
@@ -689,6 +691,19 @@ struct qse_httpd_urs_t
 
 /* -------------------------------------------------------------------------- */
 
+/* ensure to define qse_httpd_fncptr_t to the same as 
+ * qse_pio_fncptr_t in  */
+typedef int (*qse_httpd_fncptr_t) (void* ctx, qse_env_char_t** envir);
+
+typedef struct qse_httpd_fnc_t qse_httpd_fnc_t;
+struct qse_httpd_fnc_t
+{
+	qse_httpd_fncptr_t ptr;
+	void* ctx;
+};
+
+/* -------------------------------------------------------------------------- */
+
 /**
  * The qse_httpd_rsrc_type_t defines the resource type than can 
  * be entasked with qse_httpd_entaskrsrc().
@@ -702,7 +717,6 @@ enum qse_httpd_rsrc_type_t
 	QSE_HTTPD_RSRC_FILE,
 	QSE_HTTPD_RSRC_PROXY,
 	QSE_HTTPD_RSRC_RELOC,
-	QSE_HTTPD_RSRC_REDIR,
 	QSE_HTTPD_RSRC_TEXT
 };
 typedef enum qse_httpd_rsrc_type_t qse_httpd_rsrc_type_t;
@@ -711,16 +725,39 @@ enum qse_httpd_rsrc_flag_t
 {
 	QSE_HTTPD_RSRC_100_CONTINUE = (1 << 0)
 };
+typedef enum qse_httpd_rsrc_flag_t qse_httpd_rsrc_flag_t;
+
+enum qse_httpd_rsrc_cgi_flag_t
+{
+	/* non-parsed header */
+	QSE_HTTPD_RSRC_CGI_NPH = (1 << 0), 
+
+	/* 'path' points to qse_httpd_fncptr_t && 'shebang' points to its context */
+	QSE_HTTPD_RSRC_CGI_FNC = (1 << 1)
+};
+typedef enum qse_httpd_rsrc_cgi_flag_t qse_httpd_rsrc_cgi_flag_t;
+
 
 typedef struct qse_httpd_rsrc_cgi_t qse_httpd_rsrc_cgi_t;
 struct qse_httpd_rsrc_cgi_t
 {
-	const qse_mchar_t* path;
-	const qse_mchar_t* script;
-	const qse_mchar_t* suffix;
+	/* bitwised-ORed of #qse_httpd_rsrc_cgi_flag_t */
+	int flags;
+
+	/* script path resolved against file system */
+	const qse_mchar_t* path; 
+
+	/* script path as in qpath */
+	const qse_mchar_t* script; 
+
+	/* trailing part of qpath excluding the script path.
+	 * for a qpath of /tmp/abc.cgi/a/b/c, if /tmp/abc.cgi is a script path,
+	 * /a/b/c forms the suffix.*/
+	const qse_mchar_t* suffix; 
+
 	const qse_mchar_t* root;
+
 	const qse_mchar_t* shebang; 
-	int nph;
 };
 
 enum qse_httpd_rsrc_proxy_flag_t
@@ -733,6 +770,7 @@ enum qse_httpd_rsrc_proxy_flag_t
 	QSE_HTTPD_RSRC_PROXY_DNS_SERVER      = (1 << 5), /* dns address specified */
 	QSE_HTTPD_RSRC_PROXY_URS_SERVER      = (1 << 6), /* urs address specified */
 };
+typedef enum qse_httpd_rsrc_proxy_flag_t qse_httpd_rsrc_proxy_flag_t;
 
 typedef struct qse_httpd_rsrc_proxy_t qse_httpd_rsrc_proxy_t;
 struct qse_httpd_rsrc_proxy_t
@@ -774,6 +812,21 @@ struct qse_httpd_rsrc_dir_t
 	const qse_mchar_t* foot;
 };
 
+enum qse_httpd_rsrc_reloc_flag_t
+{
+	QSE_HTTPD_RSRC_RELOC_PERMANENT = (1 << 0),
+	QSE_HTTPD_RSRC_RELOC_KEEPMETHOD = (1 << 1),
+	QSE_HTTPD_RSRC_RELOC_APPENDSLASH = (1 << 2)
+};
+typedef enum qse_httpd_rsrc_reloc_flag_t qse_httpd_rsrc_reloc_flag_t;
+
+typedef struct qse_httpd_rsrc_reloc_t qse_httpd_rsrc_reloc_t;
+struct qse_httpd_rsrc_reloc_t
+{
+	int flags;
+	const qse_mchar_t* dst;
+};
+
 typedef struct qse_httpd_rsrc_t qse_httpd_rsrc_t;
 struct qse_httpd_rsrc_t
 {
@@ -802,15 +855,7 @@ struct qse_httpd_rsrc_t
 
 		qse_httpd_rsrc_proxy_t proxy;
 
-		struct
-		{
-			const qse_mchar_t* dst;
-		} reloc;
-
-		struct
-		{
-			const qse_mchar_t* dst;
-		} redir;
+		qse_httpd_rsrc_reloc_t reloc;
 
 		struct
 		{
@@ -1071,22 +1116,13 @@ QSE_EXPORT qse_httpd_task_t* qse_httpd_entaskauth (
 );
 
 QSE_EXPORT qse_httpd_task_t* qse_httpd_entaskreloc (
-	qse_httpd_t*              httpd,
-	qse_httpd_client_t*       client,
-	qse_httpd_task_t*         pred,
-	const qse_mchar_t*        dst,
-	qse_htre_t*               req
+	qse_httpd_t*                  httpd,
+	qse_httpd_client_t*           client,
+	qse_httpd_task_t*             pred,
+	const qse_httpd_rsrc_reloc_t* reloc,
+	qse_htre_t*                   req
 );
 
-QSE_EXPORT qse_httpd_task_t* qse_httpd_entaskredir (
-	qse_httpd_t*              httpd,
-	qse_httpd_client_t*       client,
-	qse_httpd_task_t*         pred,
-	const qse_mchar_t*        dst,
-	qse_htre_t*               req
-);
-
-
 QSE_EXPORT qse_httpd_task_t* qse_httpd_entasknomod (
 	qse_httpd_t*              httpd,
 	qse_httpd_client_t*       client,
diff --git a/qse/include/qse/http/stdhttpd.h b/qse/include/qse/http/stdhttpd.h
index 3e8ff971..2de606e3 100644
--- a/qse/include/qse/http/stdhttpd.h
+++ b/qse/include/qse/http/stdhttpd.h
@@ -90,9 +90,17 @@ struct qse_httpd_serverstd_auth_t
 typedef struct qse_httpd_serverstd_cgi_t qse_httpd_serverstd_cgi_t;
 struct qse_httpd_serverstd_cgi_t
 {
-	int cgi: 1;
-	int nph: 1;
-	const qse_mchar_t* shebang; /* optional, can be #QSE_NULL */
+	unsigned int cgi: 1;
+	unsigned int nph: 1;
+
+	/* optional, can be #QSE_NULL. */
+	qse_httpd_fncptr_t fncptr;
+
+	/* optional, can be #QSE_NULL. if fncptr not #QSE_NULL, shebang is
+	 * interpreted as void* and used as a context pointer to fnc. 
+	 * if fncptr is #QSE_NULL, it provides a pointer to the path 
+	 * to the program interpreter for executing a cgi script. */
+	const qse_mchar_t* shebang; 
 };
 
 typedef struct qse_httpd_serverstd_index_t qse_httpd_serverstd_index_t;
@@ -135,7 +143,22 @@ struct qse_httpd_serverstd_query_info_t
 {
 	qse_httpd_client_t* client;
 	qse_htre_t* req;
-	qse_mchar_t* xpath; /* query path combined with document root */
+
+	/** 
+	 * set to a query path combined with document root for these query types.
+	 * - #QSE_HTTPD_SERVERSTD_CGI
+	 * - #QSE_HTTPD_SERVERSTD_MIME
+	 * - #QSE_HTTPD_SERVERSTD_DIRACC
+	 * - #QSE_HTTPD_SERVERSTD_FILEACC
+	 * - #QSE_HTTPD_SERVERSTD_DIRHEAD
+	 * - #QSE_HTTPD_SERVERSTD_DIRFOOT
+	 *
+	 * set to #QSE_NULL for other query types.
+	 */
+	qse_mchar_t* xpath;
+ 
+	/** indiates that stat() failed over xpath when it's not #QSE_NULL. */
+	int xpath_nx;
 };
 typedef struct qse_httpd_serverstd_query_info_t qse_httpd_serverstd_query_info_t;
 
diff --git a/qse/lib/cmn/env.c b/qse/lib/cmn/env.c
index d9eddceb..91a6fe24 100644
--- a/qse/lib/cmn/env.c
+++ b/qse/lib/cmn/env.c
@@ -437,7 +437,7 @@ static qse_wchar_t* get_env (qse_env_t* env, const qse_wchar_t* name, int* free)
 				*free = 0;
 				return eq + 1;
 			}
-			p++;	
+			p++;
 		}
 	}
 	*/
@@ -464,10 +464,10 @@ static qse_wchar_t* get_env (qse_env_t* env, const qse_wchar_t* name, int* free)
 
 			QSE_MMGR_FREE (env->mmgr, dup);
 
-			p++;	
+			p++;
 		}
 	}
-				
+
 	return 0;
 }
 
@@ -494,10 +494,10 @@ static qse_mchar_t* get_env (qse_env_t* env, const qse_mchar_t* name, int* free)
 				*free = 0;
 				return eq + 1;
 			}
-			p++;	
+			p++;
 		}
 	}
-				
+
 	return 0;
 }
 #endif
@@ -587,14 +587,14 @@ static int load_curenv (qse_env_t* env)
 		if (*envstr != QSE_WT('=') &&
 		    add_envstrw (env, envstr) <= -1) { ret = -1; goto done; }
 		envstr += qse_wcslen (envstr) + 1;
-	}		
+	}
 #else
 	while (*envstr != QSE_MT('\0'))
 	{
 		if (*envstr != QSE_MT('=') &&
 		    add_envstrm (env, envstr) <= -1) { ret = -1; goto done; }
 		envstr += qse_mbslen (envstr) + 1;
-	}		
+	}
 #endif
 
 done:
@@ -614,7 +614,7 @@ done:
 		while (*p)
 		{
 			if (add_envstrw (env, *p) <= -1) return -1;
-			p++;	
+			p++;
 		}
 	}
 	*/
@@ -635,11 +635,10 @@ done:
 			QSE_MMGR_FREE (env->mmgr, dup);
 			if (n <= -1) return -1;
 
-			p++;	
+			p++;
 		}
 	}
-				
-				
+
 	return 0;
 
 #else
@@ -656,10 +655,10 @@ done:
 		while (*p)
 		{
 			if (add_envstrm (env, *p) <= -1) return -1;
-			p++;	
+			p++;
 		}
 	}
-				
+
 	return 0;
 #endif
 }
diff --git a/qse/lib/cmn/pio.c b/qse/lib/cmn/pio.c
index cc7f2a5a..0df45700 100644
--- a/qse/lib/cmn/pio.c
+++ b/qse/lib/cmn/pio.c
@@ -376,8 +376,8 @@ static int close_unneeded_fds_using_proc (int* excepts, qse_size_t count)
 		qse_mchar_t buf[64];
 		qse_mbsxfmt (buf, QSE_COUNTOF(buf), QSE_MT("/proc/%d/fd"), QSE_GETPID());
 		d = QSE_OPENDIR (buf);
+		if (!d) d = QSE_OPENDIR (QSE_MT("/dev/fd")); /* Darwin, FreeBSD */
 	}
-/* TODO: try /dev/fd - FreeBSD, Darwin, OS X*/
 
 	if (d)
 	{
@@ -438,10 +438,9 @@ static int get_highest_fd (void)
 		qse_mchar_t buf[64];
 		qse_mbsxfmt (buf, QSE_COUNTOF(buf), QSE_MT("/proc/%d/fd"), QSE_GETPID());
 		d = QSE_OPENDIR (buf);
+		if (!d) d = QSE_OPENDIR (QSE_MT("/dev/fd")); /* Darwin, FreeBSD */
 	}
 
-/* TODO: try /dev/fd - FreeBSD, Darwin, OS X*/
-
 	if (d)
 	{
 		int maxfd = -1;
@@ -476,7 +475,7 @@ static int get_highest_fd (void)
 	#endif
 	}
 	else fd = rlim.rlim_max;
-	if (fd == -1) fd = 1024; /* fallback */
+	if (fd <= -1) fd = 1024; /* fallback */
 
 	/* F_MAXFD is the highest fd. but RLIMIT_NOFILE and 
 	 * _SC_OPEN_MAX returnes the maximum number of file 
@@ -486,6 +485,152 @@ static int get_highest_fd (void)
 	return fd;
 }
 
+
+static qse_pio_pid_t standard_fork_and_exec (qse_pio_t* pio, int pipes[], param_t* param, qse_env_t* env)
+{
+	qse_pio_pid_t pid;
+
+#if defined(HAVE_CRT_EXTERNS_H)
+#	define environ (*(_NSGetEnviron()))
+#else
+	extern char** environ;
+#endif
+
+	pid = QSE_FORK();
+	if (pid <= -1) 
+	{
+		pio->errnum = QSE_PIO_EINVAL;
+		return -1;
+	}
+
+	if (pid == 0)
+	{
+		/* child */
+		qse_pio_hnd_t devnull = -1;
+
+		if (!(pio->flags & QSE_PIO_NOCLOEXEC))
+		{
+			if (close_unneeded_fds_using_proc (pipes, 6) <= -1)
+			{
+				int fd = get_highest_fd ();
+
+				/* close all other unknown open handles except 
+				 * stdin/out/err and the pipes. */
+				while (fd > 2)
+				{
+					if (fd != pipes[0] && fd != pipes[1] &&
+						fd != pipes[2] && fd != pipes[3] &&
+						fd != pipes[4] && fd != pipes[5]) 
+					{
+						QSE_CLOSE (fd);
+					}
+					fd--;
+				}
+			}
+		}
+
+		if (pio->flags & QSE_PIO_WRITEIN)
+		{
+			/* child should read */
+			QSE_CLOSE (pipes[1]);
+			pipes[1] = QSE_PIO_HND_NIL;
+			if (QSE_DUP2 (pipes[0], 0) <= -1) goto child_oops;
+			QSE_CLOSE (pipes[0]);
+			pipes[0] = QSE_PIO_HND_NIL;
+		}
+
+		if (pio->flags & QSE_PIO_READOUT)
+		{
+			/* child should write */
+			QSE_CLOSE (pipes[2]);
+			pipes[2] = QSE_PIO_HND_NIL;
+			if (QSE_DUP2 (pipes[3], 1) <= -1) goto child_oops;
+
+			if (pio->flags & QSE_PIO_ERRTOOUT)
+			{
+				if (QSE_DUP2 (pipes[3], 2) <= -1) goto child_oops;
+			}
+
+			QSE_CLOSE (pipes[3]); 
+			pipes[3] = QSE_PIO_HND_NIL;
+		}
+
+		if (pio->flags & QSE_PIO_READERR)
+		{
+			/* child should write */
+			QSE_CLOSE (pipes[4]); 
+			pipes[4] = QSE_PIO_HND_NIL;
+			if (QSE_DUP2 (pipes[5], 2) <= -1) goto child_oops;
+
+			if (pio->flags & QSE_PIO_OUTTOERR)
+			{
+				if (QSE_DUP2 (pipes[5], 1) <= -1) goto child_oops;
+			}
+
+			QSE_CLOSE (pipes[5]);
+			pipes[5] = QSE_PIO_HND_NIL;
+		}
+
+		if ((pio->flags & QSE_PIO_INTONUL) || 
+		    (pio->flags & QSE_PIO_OUTTONUL) ||
+		    (pio->flags & QSE_PIO_ERRTONUL))
+		{
+		#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);
+		#endif
+			if (devnull <= -1) goto child_oops;
+		}
+
+		if ((pio->flags & QSE_PIO_INTONUL)  &&
+		    QSE_DUP2(devnull,0) <= -1) goto child_oops;
+		if ((pio->flags & QSE_PIO_OUTTONUL) &&
+		    QSE_DUP2(devnull,1) <= -1) goto child_oops;
+		if ((pio->flags & QSE_PIO_ERRTONUL) &&
+		    QSE_DUP2(devnull,2) <= -1) goto child_oops;
+
+		if ((pio->flags & QSE_PIO_INTONUL) || 
+		    (pio->flags & QSE_PIO_OUTTONUL) ||
+		    (pio->flags & QSE_PIO_ERRTONUL)) 
+		{
+			QSE_CLOSE (devnull);
+			devnull = -1;
+		}
+
+		if (pio->flags & QSE_PIO_DROPIN) QSE_CLOSE(0);
+		if (pio->flags & QSE_PIO_DROPOUT) QSE_CLOSE(1);
+		if (pio->flags & QSE_PIO_DROPERR) QSE_CLOSE(2);
+
+
+		if (pio->flags & QSE_PIO_FNCCMD)
+		{
+			/* -----------------------------------------------
+			 * the function pointer to execute has been given.
+			 * -----------------------------------------------*/
+			qse_pio_fnc_t* fnc = (qse_pio_fnc_t*)param;
+			int retx;
+
+			retx = fnc->ptr (fnc->ctx, (env? qse_env_getarr(env): environ));
+			if (devnull >= 0) QSE_CLOSE (devnull);
+			QSE_EXIT (retx);
+		}
+		else
+		{
+			QSE_EXECVE (param->argv[0], param->argv, (env? qse_env_getarr(env): environ));
+
+			/* if exec fails, free 'param' parameter which is an inherited pointer */
+			free_param (pio, param); 
+		}
+
+	child_oops:
+		if (devnull >= 0) QSE_CLOSE (devnull);
+		QSE_EXIT (128);
+	}
+
+	return pid;
+}
+
 #endif
 
 static int set_pipe_nonblock (qse_pio_t* pio, qse_pio_hnd_t fd, int enabled)
@@ -1006,7 +1151,7 @@ create_process:
 		DosClose (old_out); old_out = QSE_PIO_HND_NIL;
 		DosClose (old_in); old_in = QSE_PIO_HND_NIL;
 		goto oops;
-	}    	
+	}
 
 	/* we must not let our own stdin/out/err duplicated 
 	 * into old_in/out/err be inherited */
@@ -1200,462 +1345,11 @@ create_process:
 	pio->child = child_rc.codeTerminate;
 
 #elif defined(__DOS__)
-		
+
 	/* DOS not multi-processed. can't support pio */
 	pio->errnum = QSE_PIO_ENOIMPL;
 	return -1;
 
-#elif defined(HAVE_POSIX_SPAWN) && !(defined(QSE_SYSCALL0) && defined(SYS_vfork))
-	if (flags & QSE_PIO_WRITEIN)
-	{
-		if (QSE_PIPE(&handle[0]) <= -1) 
-		{
-			pio->errnum = syserr_to_errnum (errno);
-			goto oops;
-		}
-		minidx = 0; maxidx = 1;
-	}
-
-	if (flags & QSE_PIO_READOUT)
-	{
-		if (QSE_PIPE(&handle[2]) <= -1) 
-		{
-			pio->errnum = syserr_to_errnum (errno);
-			goto oops;
-		}
-		if (minidx == -1) minidx = 2;
-		maxidx = 3;
-	}
-
-	if (flags & QSE_PIO_READERR)
-	{
-		if (QSE_PIPE(&handle[4]) <= -1) 
-		{
-			pio->errnum = syserr_to_errnum (errno);
-			goto oops;
-		}
-		if (minidx == -1) minidx = 4;
-		maxidx = 5;
-	}
-
-	if (maxidx == -1) 
-	{
-		pio->errnum = QSE_PIO_EINVAL;
-		goto oops;
-	}
-
-	if ((pserr = posix_spawn_file_actions_init (&fa)) != 0) 
-	{
-		pio->errnum = syserr_to_errnum (pserr);
-		goto oops;
-	}
-	fa_inited = 1;
-
-	if (flags & QSE_PIO_WRITEIN)
-	{
-		/* child should read */
-		if ((pserr = posix_spawn_file_actions_addclose (&fa, handle[1])) != 0) 
-		{
-			pio->errnum = syserr_to_errnum (pserr);
-			goto oops;
-		}
-		if ((pserr = posix_spawn_file_actions_adddup2 (&fa, handle[0], 0)) != 0)
-		{
-			pio->errnum = syserr_to_errnum (pserr);
-			goto oops;
-		}
-		if ((pserr = posix_spawn_file_actions_addclose (&fa, handle[0])) != 0) 
-		{
-			pio->errnum = syserr_to_errnum (pserr);
-			goto oops;
-		}
-	}
-
-	if (flags & QSE_PIO_READOUT)
-	{
-		/* child should write */
-		if ((pserr = posix_spawn_file_actions_addclose (&fa, handle[2])) != 0)
-		{
-			pio->errnum = syserr_to_errnum (pserr);
-			goto oops;
-		}
-		if ((pserr = posix_spawn_file_actions_adddup2 (&fa, handle[3], 1)) != 0)
-		{
-			pio->errnum = syserr_to_errnum (pserr);
-			goto oops;
-		}
-		if ((flags & QSE_PIO_ERRTOOUT) &&
-		    (pserr = posix_spawn_file_actions_adddup2 (&fa, handle[3], 2)) != 0)
-		{
-			pio->errnum = syserr_to_errnum (pserr);
-			goto oops;
-		}
-		if ((pserr = posix_spawn_file_actions_addclose (&fa, handle[3])) != 0)
-		{
-			pio->errnum = syserr_to_errnum (pserr);
-			goto oops;
-		}
-	}
-
-	if (flags & QSE_PIO_READERR)
-	{
-		/* child should write */
-		if ((pserr = posix_spawn_file_actions_addclose (&fa, handle[4])) != 0)
-		{
-			pio->errnum = syserr_to_errnum (pserr);
-			goto oops;
-		}
-		if ((pserr = posix_spawn_file_actions_adddup2 (&fa, handle[5], 2)) != 0)
-		{
-			pio->errnum = syserr_to_errnum (pserr);
-			goto oops;
-		}
-		if ((flags & QSE_PIO_OUTTOERR) &&
-		    (pserr = posix_spawn_file_actions_adddup2 (&fa, handle[5], 1)) != 0)
-		{
-			pio->errnum = syserr_to_errnum (pserr);
-			goto oops;
-		}
-		if ((pserr = posix_spawn_file_actions_addclose (&fa, handle[5])) != 0) 
-		{
-			pio->errnum = syserr_to_errnum (pserr);
-			goto oops;
-		}
-	}
-
-	{
-		int oflags = O_RDWR;
-	#if defined(O_LARGEFILE)
-		oflags |= O_LARGEFILE;
-	#endif
-
-		if ((flags & QSE_PIO_INTONUL) &&
-		    (pserr = posix_spawn_file_actions_addopen (&fa, 0, QSE_MT("/dev/null"), oflags, 0)) != 0)
-		{
-			pio->errnum = syserr_to_errnum (pserr);
-			goto oops;
-		}
-		if ((flags & QSE_PIO_OUTTONUL) &&
-		    (pserr = posix_spawn_file_actions_addopen (&fa, 1, QSE_MT("/dev/null"), oflags, 0)) != 0)
-		{
-			pio->errnum = syserr_to_errnum (pserr);
-			goto oops;
-		}
-		if ((flags & QSE_PIO_ERRTONUL) &&
-		    (pserr = posix_spawn_file_actions_addopen (&fa, 2, QSE_MT("/dev/null"), oflags, 0)) != 0)
-		{
-			pio->errnum = syserr_to_errnum (pserr);
-			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) &&
-	    (pserr = posix_spawn_file_actions_addclose (&fa, 0)) != 0) 
-	{
-		pio->errnum = syserr_to_errnum (pserr);
-		goto oops;
-	}
-	if ((flags & QSE_PIO_DROPOUT) && is_fd_valid(1) &&
-	    (pserr = posix_spawn_file_actions_addclose (&fa, 1)) != 0) 
-	{
-		pio->errnum = syserr_to_errnum (pserr);
-		goto oops;
-	}
-	if ((flags & QSE_PIO_DROPERR) && is_fd_valid(2) &&
-	    (pserr = posix_spawn_file_actions_addclose (&fa, 2)) != 0)
-	{
-		pio->errnum = syserr_to_errnum (pserr);
-		goto oops;
-	}
-
-	if (!(flags & QSE_PIO_NOCLOEXEC))
-	{
-		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.
-				 * posix_spawn() fails if the file descriptor added
-				 * with addclose() is closed before posix_spawn().
-				 * addclose() if no FD_CLOEXEC is set or it's unknown. */
-				if (is_fd_valid_and_nocloexec(fd) && 
-				    (pserr = posix_spawn_file_actions_addclose (&fa, fd)) != 0) 
-				{
-					pio->errnum = syserr_to_errnum (pserr);
-					goto oops;
-				}
-			}
-			fd--;
-		}
-	}
-
-	if (make_param (pio, cmd, flags, ¶m) <= -1) goto oops;
-
-	/* check if the command(the command requested or /bin/sh) is 
-	 * exectuable to return an error without trying to execute it
-	 * though this check alone isn't sufficient */
-	if (assert_executable (pio, param.argv[0]) <= -1)
-	{
-		free_param (pio, ¶m); 
-		goto oops;
-	}
-
-	posix_spawnattr_init (&psattr);
-#if defined(__linux)
-#if !defined(POSIX_SPAWN_USEVFORK)
-#	define POSIX_SPAWN_USEVFORK 0x40
-#endif
-	posix_spawnattr_setflags (&psattr, POSIX_SPAWN_USEVFORK);
-#endif
-
-	pserr = posix_spawn(
-		&pid, param.argv[0], &fa, &psattr, param.argv,
-		(env? qse_env_getarr(env): environ));
-
-#if defined(__linux)
-	posix_spawnattr_destroy (&psattr);
-#endif
-
-	free_param (pio, ¶m); 
-	if (fa_inited) 
-	{
-		posix_spawn_file_actions_destroy (&fa);
-		fa_inited = 0;
-	}
-	if (pserr != 0) 
-	{
-		pio->errnum = syserr_to_errnum (pserr);
-		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;
-	}
-
-#elif defined(QSE_SYSCALL0) && defined(SYS_vfork)
-	if (flags & QSE_PIO_WRITEIN)
-	{
-		if (QSE_PIPE(&handle[0]) <= -1) 
-		{
-			pio->errnum = syserr_to_errnum (errno);
-			goto oops;
-		}
-		minidx = 0; maxidx = 1;
-	}
-
-	if (flags & QSE_PIO_READOUT)
-	{
-		if (QSE_PIPE(&handle[2]) <= -1) 
-		{
-			pio->errnum = syserr_to_errnum (errno);
-			goto oops;
-		}
-		if (minidx == -1) minidx = 2;
-		maxidx = 3;
-	}
-
-	if (flags & QSE_PIO_READERR)
-	{
-		if (QSE_PIPE(&handle[4]) <= -1) 
-		{
-			pio->errnum = syserr_to_errnum (errno);
-			goto oops;
-		}
-		if (minidx == -1) minidx = 4;
-		maxidx = 5;
-	}
-
-	if (maxidx == -1) 
-	{
-		pio->errnum = QSE_PIO_EINVAL;
-		goto oops;
-	}
-
-	if (make_param (pio, cmd, flags, ¶m) <= -1) goto oops;
-
-	/* check if the command(the command requested or /bin/sh) is 
-	 * exectuable to return an error without trying to execute it
-	 * though this check alone isn't sufficient */
-	if (assert_executable (pio, param.argv[0]) <= -1)
-	{
-		free_param (pio, ¶m); 
-		goto oops;
-	}
-
-	/* prepare some data before vforking for vfork limitation.
-	 * the child in vfork should not make function calls or 
-	 * change data shared with the parent. */
-	if (!(flags & QSE_PIO_NOCLOEXEC)) highest_fd = get_highest_fd ();
-	envarr = env? qse_env_getarr(env): environ;
-
-	QSE_SYSCALL0 (pid, SYS_vfork);
-	if (pid <= -1) 
-	{
-		pio->errnum = QSE_PIO_EINVAL;
-		free_param (pio, ¶m);
-		goto oops;
-	}
-
-	if (pid == 0)
-	{
-		/* the child after vfork should not make function calls.
-		 * since the system call like close() are also normal
-		 * functions, i have to use assembly macros to make
-		 * system calls. */
-
-		qse_pio_hnd_t devnull = -1;
-
-		if (!(flags & QSE_PIO_NOCLOEXEC))
-		{
-			/* cannot call close_unneeded_fds_using_proc() in the vfork() context */
-
-			int fd = highest_fd;
-
-			/* close all other unknown open handles except 
-			 * stdin/out/err and the pipes. */
-			while (fd > 2)
-			{
-				if (fd != handle[0] && fd != handle[1] &&
-				    fd != handle[2] && fd != handle[3] &&
-				    fd != handle[4] && fd != handle[5]) 
-				{
-					QSE_SYSCALL1 (dummy, SYS_close, fd);
-				}
-				fd--;
-			}
-		}
-
-		if (flags & QSE_PIO_WRITEIN)
-		{
-			/* child should read */
-			QSE_SYSCALL1 (dummy, SYS_close, handle[1]);
-			QSE_SYSCALL2 (dummy, SYS_dup2, handle[0], 0);
-			if (dummy <= -1) goto child_oops;
-			QSE_SYSCALL1 (dummy, SYS_close, handle[0]);
-		}
-
-		if (flags & QSE_PIO_READOUT)
-		{
-			/* child should write */
-			QSE_SYSCALL1 (dummy, SYS_close, handle[2]);
-			QSE_SYSCALL2 (dummy, SYS_dup2, handle[3], 1);
-			if (dummy <= -1) goto child_oops;
-
-			if (flags & QSE_PIO_ERRTOOUT)
-			{
-				QSE_SYSCALL2 (dummy, SYS_dup2, handle[3], 2);
-				if (dummy <= -1) goto child_oops;
-			}
-
-			QSE_SYSCALL1 (dummy, SYS_close, handle[3]);
-		}
-
-		if (flags & QSE_PIO_READERR)
-		{
-			/* child should write */
-			QSE_SYSCALL1 (dummy, SYS_close, handle[4]);
-			QSE_SYSCALL2 (dummy, SYS_dup2, handle[5], 2);
-			if (dummy <= -1) goto child_oops;
-
-			if (flags & QSE_PIO_OUTTOERR)
-			{
-				QSE_SYSCALL2 (dummy, SYS_dup2, handle[5], 1);
-				if (dummy <= -1) goto child_oops;
-			}
-
-			QSE_SYSCALL1 (dummy, SYS_close, handle[5]);
-		}
-
-		if ((flags & QSE_PIO_INTONUL) || 
-		    (flags & QSE_PIO_OUTTONUL) ||
-		    (flags & QSE_PIO_ERRTONUL))
-		{
-		#if defined(O_LARGEFILE)
-			QSE_SYSCALL3 (devnull, SYS_open, QSE_MT("/dev/null"), O_RDWR|O_LARGEFILE, 0);
-		#else
-			QSE_SYSCALL3 (devnull, SYS_open, QSE_MT("/dev/null"), O_RDWR, 0);
-		#endif
-			if (devnull <= -1) goto child_oops;
-		}
-
-		if (flags & QSE_PIO_INTONUL)
-		{
-			QSE_SYSCALL2 (dummy, SYS_dup2, devnull, 0);
-			if (dummy <= -1) goto child_oops;
-		}
-		if (flags & QSE_PIO_OUTTONUL)
-		{
-			QSE_SYSCALL2 (dummy, SYS_dup2, devnull, 1);
-			if (dummy <= -1) goto child_oops;
-		}
-		if (flags & QSE_PIO_ERRTONUL)
-		{
-			QSE_SYSCALL2 (dummy, SYS_dup2, devnull, 2);
-			if (dummy <= -1) goto child_oops;
-		}
-
-		if ((flags & QSE_PIO_INTONUL) || 
-		    (flags & QSE_PIO_OUTTONUL) ||
-		    (flags & QSE_PIO_ERRTONUL)) 
-		{
-			QSE_SYSCALL1 (dummy, SYS_close, devnull);
-			devnull = -1;
-		}
-
-		if (flags & QSE_PIO_DROPIN) QSE_SYSCALL1 (dummy, SYS_close, 0);
-		if (flags & QSE_PIO_DROPOUT) QSE_SYSCALL1 (dummy, SYS_close, 1);
-		if (flags & QSE_PIO_DROPERR) QSE_SYSCALL1 (dummy, SYS_close, 2);
-
-		QSE_SYSCALL3 (dummy, SYS_execve, param.argv[0], param.argv, envarr);
-		/*free_param (pio, ¶m); don't free this in the vfork version */
-
-	child_oops:
-		if (devnull >= 0) QSE_SYSCALL1 (dummy, SYS_close, devnull);
-		QSE_SYSCALL1 (dummy, SYS_exit, 128);
-	}
-
-	/* parent */
-	free_param (pio, ¶m);
-	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)
@@ -1696,137 +1390,385 @@ create_process:
 		goto oops;
 	}
 
-	if (make_param (pio, cmd, flags, ¶m) <= -1) goto oops;
-
-	/* check if the command(the command requested or /bin/sh) is 
-	 * exectuable to return an error without trying to execute it
-	 * though this check alone isn't sufficient */
-	if (assert_executable (pio, param.argv[0]) <= -1)
+	if (pio->flags & QSE_PIO_FNCCMD)
 	{
-		free_param (pio, ¶m); 
-		goto oops;
+		/* i know i'm abusing typecasting here.
+		 * cmd is supposed to be qse_pio_fnc_t*, anyway */
+		pid = standard_fork_and_exec (pio, handle, (param_t*)cmd, env);
+		if (pid <= -1) goto oops;
+		pio->child = pid;
 	}
-
-	pid = QSE_FORK();
-	if (pid <= -1) 
+	else
 	{
-		pio->errnum = QSE_PIO_EINVAL;
-		free_param (pio, ¶m);
-		goto oops;
-	}
+	#if defined(HAVE_POSIX_SPAWN) && !(defined(QSE_SYSCALL0) && defined(SYS_vfork))
 
-	if (pid == 0)
-	{
-		/* child */
-		qse_pio_hnd_t devnull = -1;
+		if ((pserr = posix_spawn_file_actions_init (&fa)) != 0) 
+		{
+			pio->errnum = syserr_to_errnum (pserr);
+			goto oops;
+		}
+		fa_inited = 1;
+
+		if (flags & QSE_PIO_WRITEIN)
+		{
+			/* child should read */
+			if ((pserr = posix_spawn_file_actions_addclose (&fa, handle[1])) != 0) 
+			{
+				pio->errnum = syserr_to_errnum (pserr);
+				goto oops;
+			}
+			if ((pserr = posix_spawn_file_actions_adddup2 (&fa, handle[0], 0)) != 0)
+			{
+				pio->errnum = syserr_to_errnum (pserr);
+				goto oops;
+			}
+			if ((pserr = posix_spawn_file_actions_addclose (&fa, handle[0])) != 0) 
+			{
+				pio->errnum = syserr_to_errnum (pserr);
+				goto oops;
+			}
+		}
+
+		if (flags & QSE_PIO_READOUT)
+		{
+			/* child should write */
+			if ((pserr = posix_spawn_file_actions_addclose (&fa, handle[2])) != 0)
+			{
+				pio->errnum = syserr_to_errnum (pserr);
+				goto oops;
+			}
+			if ((pserr = posix_spawn_file_actions_adddup2 (&fa, handle[3], 1)) != 0)
+			{
+				pio->errnum = syserr_to_errnum (pserr);
+				goto oops;
+			}
+			if ((flags & QSE_PIO_ERRTOOUT) &&
+				(pserr = posix_spawn_file_actions_adddup2 (&fa, handle[3], 2)) != 0)
+			{
+				pio->errnum = syserr_to_errnum (pserr);
+				goto oops;
+			}
+			if ((pserr = posix_spawn_file_actions_addclose (&fa, handle[3])) != 0)
+			{
+				pio->errnum = syserr_to_errnum (pserr);
+				goto oops;
+			}
+		}
+
+		if (flags & QSE_PIO_READERR)
+		{
+			/* child should write */
+			if ((pserr = posix_spawn_file_actions_addclose (&fa, handle[4])) != 0)
+			{
+				pio->errnum = syserr_to_errnum (pserr);
+				goto oops;
+			}
+			if ((pserr = posix_spawn_file_actions_adddup2 (&fa, handle[5], 2)) != 0)
+			{
+				pio->errnum = syserr_to_errnum (pserr);
+				goto oops;
+			}
+			if ((flags & QSE_PIO_OUTTOERR) &&
+				(pserr = posix_spawn_file_actions_adddup2 (&fa, handle[5], 1)) != 0)
+			{
+				pio->errnum = syserr_to_errnum (pserr);
+				goto oops;
+			}
+			if ((pserr = posix_spawn_file_actions_addclose (&fa, handle[5])) != 0) 
+			{
+				pio->errnum = syserr_to_errnum (pserr);
+				goto oops;
+			}
+		}
+
+		{
+			int oflags = O_RDWR;
+		#if defined(O_LARGEFILE)
+			oflags |= O_LARGEFILE;
+		#endif
+
+			if ((flags & QSE_PIO_INTONUL) &&
+			    (pserr = posix_spawn_file_actions_addopen (&fa, 0, QSE_MT("/dev/null"), oflags, 0)) != 0)
+			{
+				pio->errnum = syserr_to_errnum (pserr);
+				goto oops;
+			}
+			if ((flags & QSE_PIO_OUTTONUL) &&
+			    (pserr = posix_spawn_file_actions_addopen (&fa, 1, QSE_MT("/dev/null"), oflags, 0)) != 0)
+			{
+				pio->errnum = syserr_to_errnum (pserr);
+				goto oops;
+			}
+			if ((flags & QSE_PIO_ERRTONUL) &&
+			    (pserr = posix_spawn_file_actions_addopen (&fa, 2, QSE_MT("/dev/null"), oflags, 0)) != 0)
+			{
+				pio->errnum = syserr_to_errnum (pserr);
+				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) &&
+		    (pserr = posix_spawn_file_actions_addclose (&fa, 0)) != 0) 
+		{
+			pio->errnum = syserr_to_errnum (pserr);
+			goto oops;
+		}
+		if ((flags & QSE_PIO_DROPOUT) && is_fd_valid(1) &&
+		    (pserr = posix_spawn_file_actions_addclose (&fa, 1)) != 0) 
+		{
+			pio->errnum = syserr_to_errnum (pserr);
+			goto oops;
+		}
+		if ((flags & QSE_PIO_DROPERR) && is_fd_valid(2) &&
+		    (pserr = posix_spawn_file_actions_addclose (&fa, 2)) != 0)
+		{
+			pio->errnum = syserr_to_errnum (pserr);
+			goto oops;
+		}
 
 		if (!(flags & QSE_PIO_NOCLOEXEC))
 		{
-			if (close_unneeded_fds_using_proc (handle, 6) <= -1)
+			int fd = get_highest_fd ();
+			while (fd > 2)
 			{
-				int fd = get_highest_fd ();
+				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.
+					 * posix_spawn() fails if the file descriptor added
+					 * with addclose() is closed before posix_spawn().
+					 * addclose() if no FD_CLOEXEC is set or it's unknown. */
+					if (is_fd_valid_and_nocloexec(fd) && 
+					    (pserr = posix_spawn_file_actions_addclose (&fa, fd)) != 0) 
+					{
+						pio->errnum = syserr_to_errnum (pserr);
+						goto oops;
+					}
+				}
+				fd--;
+			}
+		}
+
+		if (make_param (pio, cmd, flags, ¶m) <= -1) goto oops;
+
+		/* check if the command(the command requested or /bin/sh) is 
+		 * exectuable to return an error without trying to execute it
+		 * though this check alone isn't sufficient */
+		if (assert_executable (pio, param.argv[0]) <= -1)
+		{
+			free_param (pio, ¶m); 
+			goto oops;
+		}
+
+		posix_spawnattr_init (&psattr);
+
+		#if defined(__linux)
+		#if !defined(POSIX_SPAWN_USEVFORK)
+		#	define POSIX_SPAWN_USEVFORK 0x40
+		#endif
+		posix_spawnattr_setflags (&psattr, POSIX_SPAWN_USEVFORK);
+		#endif
+
+		pserr = posix_spawn(
+			&pid, param.argv[0], &fa, &psattr, param.argv,
+			(env? qse_env_getarr(env): environ));
+
+		#if defined(__linux)
+		posix_spawnattr_destroy (&psattr);
+		#endif
+
+		free_param (pio, ¶m); 
+		if (fa_inited) 
+		{
+			posix_spawn_file_actions_destroy (&fa);
+			fa_inited = 0;
+		}
+		if (pserr != 0) 
+		{
+			pio->errnum = syserr_to_errnum (pserr);
+			goto oops;
+		}
+
+		pio->child = pid;
+
+	#elif defined(QSE_SYSCALL0) && defined(SYS_vfork)
+
+		if (make_param (pio, cmd, flags, ¶m) <= -1) goto oops;
+
+		/* check if the command(the command requested or /bin/sh) is 
+		 * exectuable to return an error without trying to execute it
+		 * though this check alone isn't sufficient */
+		if (assert_executable (pio, param.argv[0]) <= -1)
+		{
+			free_param (pio, ¶m); 
+			goto oops;
+		}
+
+		/* prepare some data before vforking for vfork limitation.
+		 * the child in vfork should not make function calls or 
+		 * change data shared with the parent. */
+		if (!(flags & QSE_PIO_NOCLOEXEC)) highest_fd = get_highest_fd ();
+		envarr = env? qse_env_getarr(env): environ;
+
+		QSE_SYSCALL0 (pid, SYS_vfork);
+		if (pid <= -1) 
+		{
+			pio->errnum = QSE_PIO_EINVAL;
+			free_param (pio, ¶m);
+			goto oops;
+		}
+
+		if (pid == 0)
+		{
+			/* the child after vfork should not make function calls.
+			 * since the system call like close() are also normal
+			 * functions, i have to use assembly macros to make
+			 * system calls. */
+
+			qse_pio_hnd_t devnull = -1;
+
+			if (!(flags & QSE_PIO_NOCLOEXEC))
+			{
+				/* cannot call close_unneeded_fds_using_proc() in the vfork() context */
+
+				int fd = highest_fd;
 
 				/* close all other unknown open handles except 
 				 * stdin/out/err and the pipes. */
 				while (fd > 2)
 				{
 					if (fd != handle[0] && fd != handle[1] &&
-						fd != handle[2] && fd != handle[3] &&
-						fd != handle[4] && fd != handle[5]) 
+					    fd != handle[2] && fd != handle[3] &&
+					    fd != handle[4] && fd != handle[5]) 
 					{
-						QSE_CLOSE (fd);
+						QSE_SYSCALL1 (dummy, SYS_close, fd);
 					}
 					fd--;
 				}
 			}
-		}
 
-		if (flags & QSE_PIO_WRITEIN)
-		{
-			/* child should read */
-			QSE_CLOSE (handle[1]);
-			handle[1] = QSE_PIO_HND_NIL;
-			if (QSE_DUP2 (handle[0], 0) <= -1) goto child_oops;
-			QSE_CLOSE (handle[0]);
-			handle[0] = QSE_PIO_HND_NIL;
-		}
-
-		if (flags & QSE_PIO_READOUT)
-		{
-			/* child should write */
-			QSE_CLOSE (handle[2]);
-			handle[2] = QSE_PIO_HND_NIL;
-			if (QSE_DUP2 (handle[3], 1) <= -1) goto child_oops;
-
-			if (flags & QSE_PIO_ERRTOOUT)
+			if (flags & QSE_PIO_WRITEIN)
 			{
-				if (QSE_DUP2 (handle[3], 2) <= -1) goto child_oops;
+				/* child should read */
+				QSE_SYSCALL1 (dummy, SYS_close, handle[1]);
+				QSE_SYSCALL2 (dummy, SYS_dup2, handle[0], 0);
+				if (dummy <= -1) goto child_oops;
+				QSE_SYSCALL1 (dummy, SYS_close, handle[0]);
 			}
 
-			QSE_CLOSE (handle[3]); 
-			handle[3] = QSE_PIO_HND_NIL;
-		}
-
-		if (flags & QSE_PIO_READERR)
-		{
-			/* child should write */
-			QSE_CLOSE (handle[4]); 
-			handle[4] = QSE_PIO_HND_NIL;
-			if (QSE_DUP2 (handle[5], 2) <= -1) goto child_oops;
-
-			if (flags & QSE_PIO_OUTTOERR)
+			if (flags & QSE_PIO_READOUT)
 			{
-				if (QSE_DUP2 (handle[5], 1) <= -1) goto child_oops;
+				/* child should write */
+				QSE_SYSCALL1 (dummy, SYS_close, handle[2]);
+				QSE_SYSCALL2 (dummy, SYS_dup2, handle[3], 1);
+				if (dummy <= -1) goto child_oops;
+
+				if (flags & QSE_PIO_ERRTOOUT)
+				{
+					QSE_SYSCALL2 (dummy, SYS_dup2, handle[3], 2);
+					if (dummy <= -1) goto child_oops;
+				}
+
+				QSE_SYSCALL1 (dummy, SYS_close, handle[3]);
 			}
 
-			QSE_CLOSE (handle[5]);
-			handle[5] = QSE_PIO_HND_NIL;
+			if (flags & QSE_PIO_READERR)
+			{
+				/* child should write */
+				QSE_SYSCALL1 (dummy, SYS_close, handle[4]);
+				QSE_SYSCALL2 (dummy, SYS_dup2, handle[5], 2);
+				if (dummy <= -1) goto child_oops;
+
+				if (flags & QSE_PIO_OUTTOERR)
+				{
+					QSE_SYSCALL2 (dummy, SYS_dup2, handle[5], 1);
+					if (dummy <= -1) goto child_oops;
+				}
+
+				QSE_SYSCALL1 (dummy, SYS_close, handle[5]);
+			}
+
+			if (flags & (QSE_PIO_INTONUL | QSE_PIO_OUTTONUL | QSE_PIO_ERRTONUL))
+			{
+			#if defined(O_LARGEFILE)
+				QSE_SYSCALL3 (devnull, SYS_open, QSE_MT("/dev/null"), O_RDWR|O_LARGEFILE, 0);
+			#else
+				QSE_SYSCALL3 (devnull, SYS_open, QSE_MT("/dev/null"), O_RDWR, 0);
+			#endif
+				if (devnull <= -1) goto child_oops;
+			}
+
+			if (flags & QSE_PIO_INTONUL)
+			{
+				QSE_SYSCALL2 (dummy, SYS_dup2, devnull, 0);
+				if (dummy <= -1) goto child_oops;
+			}
+			if (flags & QSE_PIO_OUTTONUL)
+			{
+				QSE_SYSCALL2 (dummy, SYS_dup2, devnull, 1);
+				if (dummy <= -1) goto child_oops;
+			}
+			if (flags & QSE_PIO_ERRTONUL)
+			{
+				QSE_SYSCALL2 (dummy, SYS_dup2, devnull, 2);
+				if (dummy <= -1) goto child_oops;
+			}
+
+			if (flags & (QSE_PIO_INTONUL | QSE_PIO_OUTTONUL | QSE_PIO_ERRTONUL))
+			{
+				QSE_SYSCALL1 (dummy, SYS_close, devnull);
+				devnull = -1;
+			}
+
+			if (flags & QSE_PIO_DROPIN) QSE_SYSCALL1 (dummy, SYS_close, 0);
+			if (flags & QSE_PIO_DROPOUT) QSE_SYSCALL1 (dummy, SYS_close, 1);
+			if (flags & QSE_PIO_DROPERR) QSE_SYSCALL1 (dummy, SYS_close, 2);
+
+			QSE_SYSCALL3 (dummy, SYS_execve, param.argv[0], param.argv, envarr);
+			/*free_param (pio, ¶m); don't free this in the vfork version */
+
+		child_oops:
+			if (devnull >= 0) QSE_SYSCALL1 (dummy, SYS_close, devnull);
+			QSE_SYSCALL1 (dummy, SYS_exit, 128);
 		}
 
-		if ((flags & QSE_PIO_INTONUL) || 
-		    (flags & QSE_PIO_OUTTONUL) ||
-		    (flags & QSE_PIO_ERRTONUL))
+		/* parent */
+		free_param (pio, ¶m);
+		pio->child = pid;
+
+	#else
+
+		if (make_param (pio, cmd, flags, ¶m) <= -1) goto oops;
+
+		/* check if the command(the command requested or /bin/sh) is 
+		 * exectuable to return an error without trying to execute it
+		 * though this check alone isn't sufficient */
+		if (assert_executable (pio, param.argv[0]) <= -1)
 		{
-		#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);
-		#endif
-			if (devnull <= -1) goto child_oops;
+			free_param (pio, ¶m); 
+			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)) 
+		pid = standard_fork_and_exec (pio, handle, ¶m, env);
+		if (pid <= -1)
 		{
-			QSE_CLOSE (devnull);
-			devnull = -1;
+			free_param (pio, ¶m);
+			goto oops;
 		}
 
-		if (flags & QSE_PIO_DROPIN) QSE_CLOSE(0);
-		if (flags & QSE_PIO_DROPOUT) QSE_CLOSE(1);
-		if (flags & QSE_PIO_DROPERR) QSE_CLOSE(2);
+		/* parent */
+		free_param (pio, ¶m);
+		pio->child = pid;
+	#endif
 
-		/*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);
 	}
 
-	/* parent */
-	free_param (pio, ¶m);
-	pio->child = pid;
-
 	if (flags & QSE_PIO_WRITEIN)
 	{
 		/* 
@@ -1862,7 +1804,6 @@ create_process:
 		QSE_CLOSE (handle[5]);
 		handle[5] = QSE_PIO_HND_NIL;
 	}
-
 #endif
 
 	if (((flags & QSE_PIO_INNOBLOCK) && set_pipe_nonblock(pio, handle[1], 1) <= -1) ||
@@ -1957,7 +1898,7 @@ oops:
 #elif defined(__OS2__)
 	for (i = minidx; i < maxidx; i++) 
 	{
-    		if (handle[i] != QSE_PIO_HND_NIL) DosClose (handle[i]);
+		if (handle[i] != QSE_PIO_HND_NIL) DosClose (handle[i]);
 	}
 #elif defined(__DOS__)
 
diff --git a/qse/lib/cmn/task.c b/qse/lib/cmn/task.c
index c7cc027c..add98bb2 100644
--- a/qse/lib/cmn/task.c
+++ b/qse/lib/cmn/task.c
@@ -316,7 +316,7 @@ qse_task_slice_t* qse_task_create (
 	tmp = ((qse_uint8_t*)(slice + 1)) + stksize - QSE_SIZEOF(void*);
 
 	tmp = (qse_uint8_t*)tmp - QSE_SIZEOF(void*);
-	*(void**)tmp = NULL; /* t1 */
+	*(void**)tmp = QSE_NULL; /* t1 */
 
 	tmp = (qse_uint8_t*)tmp - QSE_SIZEOF(void*);
 	*(void**)tmp = slice; /* t2 */
diff --git a/qse/lib/http/http.c b/qse/lib/http/http.c
index 50a5c1c6..472a25d2 100644
--- a/qse/lib/http/http.c
+++ b/qse/lib/http/http.c
@@ -56,6 +56,7 @@ const qse_mchar_t* qse_httpstatustombs (int code)
 		case 304: msg = QSE_MT("Not Modified"); break;
 		case 305: msg = QSE_MT("Use Proxy"); break;
 		case 307: msg = QSE_MT("Temporary Redirect"); break;
+		case 308: msg = QSE_MT("Permanent Redirect"); break;
 
 		case 400: msg = QSE_MT("Bad Request"); break;
 		case 401: msg = QSE_MT("Unauthorized"); break;
diff --git a/qse/lib/http/httpd-cgi.c b/qse/lib/http/httpd-cgi.c
index f2ffc69c..430c3fc4 100644
--- a/qse/lib/http/httpd-cgi.c
+++ b/qse/lib/http/httpd-cgi.c
@@ -44,6 +44,7 @@ struct task_cgi_arg_t
 	qse_mcstr_t root;
 	qse_mcstr_t shebang;
 	int nph;
+	qse_httpd_fnc_t fnc;
 	qse_htre_t* req;
 };
 
@@ -63,6 +64,7 @@ struct task_cgi_t
 	qse_http_version_t version;
 	int keepalive; /* taken from the request */
 	int nph;
+	qse_pio_fnc_t fnc;
 
 	qse_htrd_t* script_htrd;
 	qse_env_t* env;
@@ -705,7 +707,7 @@ static int task_init_cgi (
 	qse_size_t len;
 	const qse_mchar_t* ptr;
 	const qse_htre_hdrval_t* tmp;
-	
+
 	cgi = (task_cgi_t*)qse_httpd_gettaskxtn (httpd, task);
 	arg = (task_cgi_arg_t*)task->ctx;
 
@@ -733,6 +735,12 @@ static int task_init_cgi (
 	cgi->keepalive = (arg->req->flags & QSE_HTRE_ATTR_KEEPALIVE);
 	cgi->nph = arg->nph;
 	cgi->req = QSE_NULL;
+	if (arg->fnc.ptr) 
+	{
+		/* the function pointer is set */
+		cgi->fnc.ptr = arg->fnc.ptr;
+		cgi->fnc.ctx = arg->fnc.ctx;
+	}
 
 	content_length = 0;
 	if (arg->req->state & QSE_HTRE_DISCARDED) goto done;
@@ -1432,6 +1440,7 @@ static int task_main_cgi (
 		cgi_script_htrd_xtn_t* xtn;
 		cgi->script_htrd = qse_htrd_open (httpd->mmgr, QSE_SIZEOF(cgi_script_htrd_xtn_t));
 		if (cgi->script_htrd == QSE_NULL) goto oops;
+
 		xtn = (cgi_script_htrd_xtn_t*) qse_htrd_getxtn (cgi->script_htrd);
 		xtn->cgi = cgi;
 		xtn->task = task;
@@ -1460,22 +1469,30 @@ static int task_main_cgi (
 	if (httpd->opt.trait & QSE_HTTPD_CGINOCLOEXEC) 
 		pio_options |= QSE_PIO_NOCLOEXEC;
 
-	if (cgi->shebang[0] != QSE_MT('\0'))
+	if (cgi->fnc.ptr)
 	{
-		const qse_mchar_t* tmp[4];
-		tmp[0] = cgi->shebang;
-		tmp[1] = QSE_MT(" ");
-		tmp[2] = cgi->path;
-		tmp[3] = QSE_NULL;
-		xpath = qse_mbsadup (tmp, QSE_NULL, httpd->mmgr);
-		if (xpath == QSE_NULL) goto oops;
+		xpath = (qse_mchar_t*)&cgi->fnc;
+		pio_options |= QSE_PIO_FNCCMD;
+	}
+	else
+	{
+		if (cgi->shebang[0] != QSE_MT('\0'))
+		{
+			const qse_mchar_t* tmp[4];
+			tmp[0] = cgi->shebang;
+			tmp[1] = QSE_MT(" ");
+			tmp[2] = cgi->path;
+			tmp[3] = QSE_NULL;
+			xpath = qse_mbsadup (tmp, QSE_NULL, httpd->mmgr);
+			if (xpath == QSE_NULL) goto oops;
+		}
+		else xpath = cgi->path;
 	}
-	else xpath = cgi->path;
 
 	x = qse_pio_init (
 		&cgi->pio, httpd->mmgr, (const qse_char_t*)xpath,
 		cgi->env, pio_options);
-	if (xpath != cgi->path) QSE_MMGR_FREE (httpd->mmgr, xpath);
+	if (xpath != cgi->path && xpath != &cgi->fnc) QSE_MMGR_FREE (httpd->mmgr, xpath);
 
 	if (x <= -1)
 	{
@@ -1539,6 +1556,12 @@ static int task_main_cgi (
 			}
 		}
 	}
+	else
+	{
+		/* no forwarding buffer. the request should not send any contents
+		 * to the cgi script. close the input to the script */
+		qse_pio_end (&cgi->pio, QSE_PIO_IN);
+	}
 
 	task->main = cgi->nph? task_main_cgi_4_nph: task_main_cgi_2;
 	return 1;
@@ -1575,11 +1598,35 @@ qse_httpd_task_t* qse_httpd_entaskcgi (
 	task_cgi_arg_t arg;
 	qse_httpd_rsrc_cgi_t rsrc;
 
+	QSE_MEMSET (&arg, 0, QSE_SIZEOF(arg));
+
 	rsrc = *cgi;
+	if (rsrc.flags & QSE_HTTPD_RSRC_CGI_FNC)
+	{
+		/* rsrc.script must carry a pointer to qse_pio_fnc_t */
+		if (rsrc.script == QSE_NULL || ((qse_pio_fnc_t*)rsrc.script)->ptr == QSE_NULL)
+		{
+			httpd->errnum = QSE_HTTPD_EINVAL;
+			return QSE_NULL;
+		}
+
+		arg.fnc.ptr = (qse_httpd_fncptr_t)rsrc.path;
+		arg.fnc.ctx = (void*)rsrc.shebang;
+
+		/* reset the script to an empty string for less interference 
+		 * with code handling normal script */
+		rsrc.path = QSE_MT("");
+		rsrc.shebang = QSE_MT("");
+	}
+	else
+	{
+		QSE_ASSERT (rsrc.path != QSE_NULL);
+		if (rsrc.shebang == QSE_NULL) rsrc.shebang = QSE_MT("");
+	}
+
 	if (rsrc.script == QSE_NULL) rsrc.script = qse_htre_getqpath(req);
 	if (rsrc.suffix == QSE_NULL) rsrc.suffix = QSE_MT("");
 	if (rsrc.root == QSE_NULL) rsrc.root = QSE_MT("");
-	if (rsrc.shebang == QSE_NULL) rsrc.shebang = QSE_MT("");
 
 	arg.path.ptr = (qse_mchar_t*)rsrc.path;
 	arg.path.len = qse_mbslen(rsrc.path);
@@ -1589,7 +1636,7 @@ qse_httpd_task_t* qse_httpd_entaskcgi (
 	arg.suffix.len = qse_mbslen(rsrc.suffix);
 	arg.root.ptr = (qse_mchar_t*)rsrc.root;
 	arg.root.len = qse_mbslen(rsrc.root);
-	arg.nph = rsrc.nph;
+	arg.nph = ((rsrc.flags & QSE_HTTPD_RSRC_CGI_NPH) != 0);
 	arg.shebang.ptr = (qse_mchar_t*)rsrc.shebang;
 	arg.shebang.len = qse_mbslen(rsrc.shebang);
 	arg.req = req;
diff --git a/qse/lib/http/httpd-proxy.c b/qse/lib/http/httpd-proxy.c
index ccd0d62b..a2cf7c69 100644
--- a/qse/lib/http/httpd-proxy.c
+++ b/qse/lib/http/httpd-proxy.c
@@ -1983,7 +1983,7 @@ printf ("XXXXXXXXXXXXXXXXXXXXXXXXXX URL REWRITTEN TO [%s].....\n", new_url);
 		{
 			/* check if it begins with redirection code followed by a colon */
 			int redir_code = 0;
-			qse_httpd_status_reloc_t reloc;
+			qse_httpd_rsrc_reloc_t reloc;
 			const qse_mchar_t* nuptr = new_url;
 			do
 			{
@@ -1996,11 +1996,14 @@ printf ("XXXXXXXXXXXXXXXXXXXXXXXXXX URL REWRITTEN TO [%s].....\n", new_url);
 				/* no colon is found after digits. it's probably a normal url */
 				goto normal_url;
 			}
-			if (redir_code != 301 && redir_code != 302 && redir_code != 307) redir_code = 301;
+			if (redir_code != 301 && redir_code != 302 && redir_code != 303 && 
+			    redir_code != 307 && redir_code != 308) redir_code = 302;
 			nuptr++;
 
+			/* relocation code is given explictly, no slash appending is needed.
+			 * use qse_httpd_entask_status() rather than qse_httpd_entaskreloc(). */
+			reloc.flags = 0; 
 			reloc.dst = nuptr;
-			reloc.redir = 0; /* don't want to append extra / */
 
 			if (qse_httpd_entask_status (
 				httpd, proxy->client, proxy->task, redir_code, &reloc,
diff --git a/qse/lib/http/httpd-std.c b/qse/lib/http/httpd-std.c
index 59ece36f..3fe3752b 100644
--- a/qse/lib/http/httpd-std.c
+++ b/qse/lib/http/httpd-std.c
@@ -1547,7 +1547,7 @@ static int file_stat (
 	 * for a file. it is mainly used to get a file size and timestamps
 	 * of a regular file. so it should fail for a non-regular file.
 	 * note that STAT_REG is passed to stat_file for it */
-	return stat_file (httpd, path, hst, STAT_REG);	
+	return stat_file (httpd, path, hst, STAT_REG);
 }
 
 static int file_purge (qse_httpd_t* httpd, const qse_mchar_t* path)
@@ -2444,10 +2444,13 @@ static void free_resource (
 				QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.cgi.suffix);
 			if (target->u.cgi.script != qpath) 
 				QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.cgi.script);
-			if (target->u.cgi.path != qpath)
-				QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.cgi.path);
-			if (target->u.cgi.shebang)
-				QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.cgi.shebang);
+			if (!(target->u.cgi.flags & QSE_HTTPD_RSRC_CGI_FNC))
+			{
+				if (target->u.cgi.path != qpath)
+					QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.cgi.path);
+				if (target->u.cgi.shebang)
+					QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.cgi.shebang);
+			}
 
 			break;
 
@@ -2457,8 +2460,8 @@ static void free_resource (
 			break;
 
 		case QSE_HTTPD_RSRC_FILE:
-			if (target->u.cgi.path != qpath)
-				QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.cgi.path);
+			if (target->u.file.path != qpath)
+				QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.file.path);
 			break;
 
 		case QSE_HTTPD_RSRC_RELOC:
@@ -2466,11 +2469,6 @@ static void free_resource (
 				QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.reloc.dst);
 			break;
 
-		case QSE_HTTPD_RSRC_REDIR:
-			if (target->u.redir.dst != qpath)
-				QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.redir.dst);
-			break;
-
 		default:
 			/* nothing to do */
 			break;
@@ -2573,22 +2571,19 @@ static int attempt_cgi (
 				#endif
 
 				/* create a relocation resource */
+				QSE_MEMSET (target, 0, QSE_SIZEOF(*target));
 				target->type = QSE_HTTPD_RSRC_RELOC;
 				target->u.reloc.dst = merge_paths (httpd, tmp->qpath, tmp->idxfile);
 				if (target->u.reloc.dst == QSE_NULL) goto oops;
+
 				/* free tmp->xpath here upon success since it's not used for relocation.
-				 * it is freed by the called upon failure so the 'oops' part don't free it */
+				 * upon failure, it is freed by the caller. so the 'oops' part 
+				 * of this function doesn't free it. */
 				QSE_MMGR_FREE (httpd->mmgr, tmp->xpath);
 				return 1;
 			}
 			else script = (qse_mchar_t*)tmp->qpath;
 
-			if (cgi.shebang)
-			{
-				shebang = qse_mbsdup (cgi.shebang, httpd->mmgr);
-				if (shebang == QSE_NULL) goto oops;
-			}
-
 			goto bingo;
 		}
 	}
@@ -2619,14 +2614,13 @@ static int attempt_cgi (
 					 */
 					merge_paths_to_buf (httpd, tmp->root.u.path.val, tmp->qpath_rp, slash - tmp->qpath_rp, tmp->xpath);
 					xpath_changed = 1;
-	
+
 					stx = stat_file (httpd, tmp->xpath, &st, 0);
 					if (stx <= -1) 
 					{
-						/* stop at the current segment if stating fails. 
-						 * if the current semgment can't be stat-ed, it's not likely that
-						 * the next segment can be successfully stat-ed */
-						break; 
+						/* instead of stopping here, let's give a non-existent 
+						 * segment to be a virtual cgi script(function pointer). */
+						st.isdir = 0;
 					}
 
 					if (!st.isdir)
@@ -2636,9 +2630,16 @@ static int attempt_cgi (
 						QSE_MEMSET (&qinfo, 0, QSE_SIZEOF(qinfo));
 						qinfo.req = req;
 						qinfo.xpath = tmp->xpath;
+						qinfo.xpath_nx = (stx <= -1);
 
 						if (server_xtn->query (httpd, client->server, QSE_HTTPD_SERVERSTD_CGI, &qinfo, &cgi) >= 0 && cgi.cgi)
 						{
+							if (cgi.fncptr == QSE_NULL && stx <= -1) 
+							{
+								/* normal cgi script must exist. */
+								break;
+							}
+
 							/* the script name is composed of the orginal query path.
 							 * the pointer held in 'slash' is valid for tmp->qpath as
 							 * tmp->qpath_rp is at most the tail part of tmp->qpath. */
@@ -2646,23 +2647,57 @@ static int attempt_cgi (
 							suffix = qse_mbsdup (slash, httpd->mmgr);
 							if (!script || !suffix) goto oops;
 
-							if (cgi.shebang)
-							{
-								shebang = qse_mbsdup (cgi.shebang, httpd->mmgr);
-								if (shebang == QSE_NULL) goto oops;
-							}
-
 							goto bingo;
 						}
 					}
+
+					if (stx <= -1) 
+					{
+						/* stop at the current segment if stat() fails. 
+						 * if the current segment can't be stat-ed, it's not likely that
+						 * the next segment can be stat-ed successfully */
+						break;
+					}
 				}
 
 				ptr = slash + 1;
 			}
 			else 
 			{
-				/* no more slash is found. the last segement doesn't have to be checked 
-				 * here since it's attempted by the caller. */
+				/* no more slash is found. this is the last segment.
+				 * the caller has called stat() against the last segment
+				 * before having called this function. so it's known
+				 * that the path disn't exist.
+				 * 
+				 * however, a virtual cgi script may not exist. a check
+				 * for it is still required here */
+				
+				qse_httpd_serverstd_query_info_t qinfo;
+
+				if (xpath_changed)
+				{
+					/* restore the tmp->xpath to the original value by 
+					 * combining the full path with the document root. */
+					merge_paths_to_buf (httpd, tmp->root.u.path.val, tmp->qpath_rp, (qse_size_t)-1, tmp->xpath);
+					xpath_changed = 0;
+				}
+
+				QSE_MEMSET (&qinfo, 0, QSE_SIZEOF(qinfo));
+				qinfo.req = req;
+				qinfo.xpath = tmp->xpath;
+				qinfo.xpath_nx = 1;
+
+				if (server_xtn->query (httpd, client->server, QSE_HTTPD_SERVERSTD_CGI, &qinfo, &cgi) >= 0 && cgi.cgi && cgi.fncptr)
+				{
+					/* virtual cgi script */
+					script = qse_mbsdup (tmp->qpath, httpd->mmgr);
+					if (!script) goto oops;
+					suffix = QSE_NULL;
+
+					goto bingo;
+				}
+
+				/* not a virtual cgi script. just break */
 				break;
 			}
 		}
@@ -2675,12 +2710,32 @@ static int attempt_cgi (
 
 bingo:
 	target->type = QSE_HTTPD_RSRC_CGI;
-	target->u.cgi.nph = cgi.nph;
-	target->u.cgi.path = tmp->xpath;
+	target->u.cgi.flags = 0;
+	if (cgi.nph) target->u.cgi.flags |= QSE_HTTPD_RSRC_CGI_NPH;
+
+	if (cgi.fncptr) 
+	{
+		/* the type casting here is guly */
+		target->u.cgi.path = (qse_mchar_t*)cgi.fncptr;
+		target->u.cgi.shebang = cgi.shebang;
+		target->u.cgi.flags |= QSE_HTTPD_RSRC_CGI_FNC;
+	}
+	else 
+	{
+		if (cgi.shebang)
+		{
+			shebang = qse_mbsdup (cgi.shebang, httpd->mmgr);
+			if (shebang == QSE_NULL) goto oops;
+		}
+
+		target->u.cgi.path = tmp->xpath;
+		target->u.cgi.shebang = shebang;
+	}
+
 	target->u.cgi.script = script;
 	target->u.cgi.suffix = suffix;
 	target->u.cgi.root = tmp->root.u.path.val;
-	target->u.cgi.shebang = shebang;
+
 	return 1;
 
 oops:
@@ -2739,6 +2794,13 @@ static int make_resource (
 	/* handle the request locally */
 	QSE_ASSERT (tmp.root.type == QSE_HTTPD_SERVERSTD_ROOT_PATH);
 
+/*****************************************************************************
+ * BUG BUG BUG !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
+ * TODO: calling the realm query here is wrong especially if the prefix path is resolved to a cgi.
+ * for example, /abc/def/test.cgi/x/y/z, 
+ * when this function queries for REALM, it's not known that /abc/def/test.cgi is a cgi script.
+
+ *****************************************************************************/
 	if (server_xtn->query (httpd, client->server, QSE_HTTPD_SERVERSTD_REALM, &qinfo, &tmp.realm) <= -1 ||
 	    server_xtn->query (httpd, client->server, QSE_HTTPD_SERVERSTD_INDEX, &qinfo, &tmp.index) <= -1)
 	{
@@ -2877,7 +2939,7 @@ auth_ok:
 						tmp.xpath = tpath;
 						tmp.idxfile = ptr;
 						goto attempt_file;
-					}	
+					}
 
 					QSE_MMGR_FREE (httpd->mmgr, tpath);
 				}
@@ -2898,8 +2960,9 @@ auth_ok:
 			{
 				/* the query path doesn't end with a slash. so redirect it  */
 				qse_htre_discardcontent (req);
-				target->type = QSE_HTTPD_RSRC_REDIR;
-				target->u.redir.dst = tmp.qpath;
+				target->type = QSE_HTTPD_RSRC_RELOC;
+				target->u.reloc.flags = QSE_HTTPD_RSRC_RELOC_APPENDSLASH | QSE_HTTPD_RSRC_RELOC_PERMANENT;
+				target->u.reloc.dst = tmp.qpath;
 				/* free xpath since it won't be used */
 				QSE_MMGR_FREE (httpd->mmgr, tmp.xpath);
 			}
@@ -2929,7 +2992,16 @@ auth_ok:
 			QSE_MMGR_FREE (httpd->mmgr, tmp.xpath);
 			return -1;
 		}
-		if (n >= 1) return 0;
+		if (n >= 1) 
+		{
+			if (target->u.cgi.flags & QSE_HTTPD_RSRC_CGI_FNC)
+			{
+				/* tmp.xpath is not set to target->u.cgi.path when
+				 * this flag is set. it must be deallocated */
+				QSE_MMGR_FREE (httpd->mmgr, tmp.xpath);
+			}
+			return 0;
+		}
 
 		qinfo.xpath = tmp.xpath;
 
@@ -3015,8 +3087,8 @@ struct cgi_tab_t
 };
 static struct cgi_tab_t cgitab[] =
 {
-	{ QSE_MT(".cgi"), { 1, 0, QSE_NULL } },
-	{ QSE_MT(".nph"), { 1, 1, QSE_NULL } },
+	{ QSE_MT(".cgi"), { 1, 0, QSE_NULL, QSE_NULL } },
+	{ QSE_MT(".nph"), { 1, 1, QSE_NULL, QSE_NULL } },
 };
 
 static int query_server (
@@ -3071,12 +3143,18 @@ static int query_server (
 		case QSE_HTTPD_SERVERSTD_CGI:
 		{
 			qse_httpd_serverstd_cgi_t* cgi = (qse_httpd_serverstd_cgi_t*)result;
-			for (i = 0; i < QSE_COUNTOF(cgitab); i++)
+
+			if (!qinfo->xpath_nx) 
 			{
-				if (qse_mbsend (qinfo->xpath, cgitab[i].suffix))
+				/* this standard implementation supports a normal cgi script only */
+
+				for (i = 0; i < QSE_COUNTOF(cgitab); i++)
 				{
-					QSE_MEMCPY (cgi, &cgitab[i].cgi, QSE_SIZEOF(*cgi));
-					return 0;
+					if (qse_mbsend (qinfo->xpath, cgitab[i].suffix))
+					{
+						QSE_MEMCPY (cgi, &cgitab[i].cgi, QSE_SIZEOF(*cgi));
+						return 0;
+					}
 				}
 			}
 
diff --git a/qse/lib/http/httpd-task.c b/qse/lib/http/httpd-task.c
index ca92d501..b533b500 100644
--- a/qse/lib/http/httpd-task.c
+++ b/qse/lib/http/httpd-task.c
@@ -178,14 +178,16 @@ qse_httpd_task_t* qse_httpd_entask_status (
 	msg = qse_httpstatustombs (code);
 	switch (code)
 	{
-		case 301:
-		case 302:
-		case 307:
+		case 301: /* Moved Permanently */ 
+		case 302: /* Found */
+		case 303: /* See Other (since HTTP/1.1) */
+		case 307: /* Temporary Redirect (since HTTP/1.1) */
+		case 308: /* Permanent Redirect (Experimental RFC; RFC 7238) */
 		{
-			qse_httpd_status_reloc_t* reloc;
-			reloc = (qse_httpd_status_reloc_t*)extra;
+			qse_httpd_rsrc_reloc_t* reloc;
+			reloc = (qse_httpd_rsrc_reloc_t*)extra;
 			extrapre = QSE_MT("Location: ");
-			extrapst = reloc->redir? QSE_MT("/\r\n"): QSE_MT("\r\n");
+			extrapst = (reloc->flags & QSE_HTTPD_RSRC_RELOC_APPENDSLASH)? QSE_MT("/\r\n"): QSE_MT("\r\n");
 			extraval = reloc->dst;
 			break;
 		}
@@ -276,31 +278,22 @@ qse_httpd_task_t* qse_httpd_entaskauth (
 
 qse_httpd_task_t* qse_httpd_entaskreloc (
 	qse_httpd_t* httpd, qse_httpd_client_t* client, 
-	qse_httpd_task_t* pred, const qse_mchar_t* dst, qse_htre_t* req)
+	qse_httpd_task_t* pred, const qse_httpd_rsrc_reloc_t* reloc, qse_htre_t* req)
 {
-	qse_httpd_status_reloc_t reloc;
+	int code;
 
-	reloc.dst = dst;
-	reloc.redir = 0;
+	if (reloc->flags & QSE_HTTPD_RSRC_RELOC_KEEPMETHOD)
+	{
+		code = (reloc->flags & QSE_HTTPD_RSRC_RELOC_PERMANENT)? 308: 307;
+	}
+	else
+	{
+		/* NOTE: 302 can be 303 for HTTP/1.1 */
+		code = (reloc->flags & QSE_HTTPD_RSRC_RELOC_PERMANENT)? 301: 302;
+	}
 
 	return qse_httpd_entask_status (
-		httpd, client, pred, 301, (void*)&reloc,
-		qse_htre_getqmethodtype(req), 
-		qse_htre_getversion(req), 
-		(req->flags & QSE_HTRE_ATTR_KEEPALIVE));
-}
-
-qse_httpd_task_t* qse_httpd_entaskredir (
-	qse_httpd_t* httpd, qse_httpd_client_t* client, 
-	qse_httpd_task_t* pred, const qse_mchar_t* dst, qse_htre_t* req)
-{
-	qse_httpd_status_reloc_t reloc;
-
-	reloc.dst = dst;
-	reloc.redir = 1;
-
-	return qse_httpd_entask_status (
-		httpd, client, pred, 301, (void*)&reloc,
+		httpd, client, pred, code, (void*)reloc,
 		qse_htre_getqmethodtype(req), 
 		qse_htre_getversion(req), 
 		(req->flags & QSE_HTRE_ATTR_KEEPALIVE));
@@ -403,11 +396,7 @@ qse_httpd_task_t* qse_httpd_entaskrsrc (
 			break;
 
 		case QSE_HTTPD_RSRC_RELOC:
-			task = qse_httpd_entaskreloc (httpd, client, pred, rsrc->u.reloc.dst, req);
-			break;
-
-		case QSE_HTTPD_RSRC_REDIR:
-			task = qse_httpd_entaskredir (httpd, client, pred, rsrc->u.redir.dst, req);
+			task = qse_httpd_entaskreloc (httpd, client, pred, &rsrc->u.reloc, req);
 			break;
 
 		case QSE_HTTPD_RSRC_TEXT:
diff --git a/qse/lib/http/httpd.h b/qse/lib/http/httpd.h
index 9e25f093..6770c200 100644
--- a/qse/lib/http/httpd.h
+++ b/qse/lib/http/httpd.h
@@ -100,13 +100,6 @@ struct qse_httpd_real_task_t
 	qse_httpd_real_task_t* next;
 };
 
-typedef struct qse_httpd_status_reloc_t qse_httpd_status_reloc_t;
-struct qse_httpd_status_reloc_t
-{
-	const qse_mchar_t* dst;
-	int redir;
-};
-
 #define MAX_SEND_SIZE 4096
 #define MAX_RECV_SIZE 4096