zorba-coders team mailing list archive
-
zorba-coders team
-
Mailing list archive
-
Message #23879
[Merge] lp:~zorba-coders/zorba/process-2 into lp:zorba
Nicolae Brinza has proposed merging lp:~zorba-coders/zorba/process-2 into lp:zorba.
Commit message:
Changed module's errors to the new modules guidelines; Potential fix for execvpe() on Macs
Requested reviews:
Nicolae Brinza (nbrinza)
Related bugs:
Bug #1188053 in Zorba: "Update non-core module "process""
https://bugs.launchpad.net/zorba/+bug/1188053
For more details, see:
https://code.launchpad.net/~zorba-coders/zorba/process-2/+merge/171822
Changed module's errors to the new modules guidelines; Potential fix for execvpe() on Macs
--
https://code.launchpad.net/~zorba-coders/zorba/process-2/+merge/171822
Your team Zorba Coders is subscribed to branch lp:zorba.
=== added file 'CMakeLists.txt'
--- CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ CMakeLists.txt 2013-06-27 14:44:39 +0000
@@ -0,0 +1,34 @@
+# Copyright 2006-2010 The FLWOR Foundation.
+#
+# 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.
+
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
+
+PROJECT (zorba_process_module)
+ENABLE_TESTING ()
+INCLUDE (CTest)
+
+LIST (APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake_modules")
+
+FIND_PACKAGE (Zorba REQUIRED HINTS "${ZORBA_BUILD_DIR}")
+INCLUDE ("${Zorba_USE_FILE}")
+
+ADD_SUBDIRECTORY("src")
+
+ADD_TEST_DIRECTORY ("${PROJECT_SOURCE_DIR}/test")
+
+IF (WIN32)
+ EXPECTED_FAILURE (zorba_process_module/process.xq 866979)
+ENDIF (WIN32)
+
+DONE_DECLARING_ZORBA_URIS()
=== renamed file 'CMakeLists.txt' => 'CMakeLists.txt.moved'
=== added directory 'src'
=== renamed directory 'src' => 'src.moved'
=== added file 'src/CMakeLists.txt'
--- src/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ src/CMakeLists.txt 2013-06-27 14:44:39 +0000
@@ -0,0 +1,16 @@
+# Copyright 2006-2013 The FLWOR Foundation.
+#
+# 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.
+
+DECLARE_ZORBA_MODULE (URI "http://zorba.io/modules/process" VERSION 1.0 FILE "process-2.xq")
+DECLARE_ZORBA_MODULE (URI "http://www.zorba-xquery.com/modules/process" VERSION 1.0 FILE "process-1.xq")
=== added file 'src/process-1.xq'
--- src/process-1.xq 1970-01-01 00:00:00 +0000
+++ src/process-1.xq 2013-06-27 14:44:39 +0000
@@ -0,0 +1,95 @@
+xquery version "3.0";
+
+(:
+ : Copyright 2006-2013 The FLWOR Foundation.
+ :
+ : 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.
+:)
+
+(:~
+ : This module provides functions to create a native process and return the result
+ : (i.e. exit code, result on standard out and error).
+ : <p>
+ : Example:
+ :<pre class="ace-static" ace-mode="xquery">
+ : import module namespace proc = "http://www.zorba-xquery.com/modules/process";
+ : proc:exec("ls")
+ :</pre>
+ : </p>
+ : <p>
+ : Potential result:
+ : <pre class="ace-static" ace-mode="xquery"><![CDATA[
+ : <result xmlns="http://www.zorba-xquery.com/modules/process">
+ : <stdout>myfile.txt</stout>
+ : <stderr/>
+ : <exit-code>0</exit-code>
+ : </result>
+ : ]]></pre>
+ : </p>
+ :
+ : @author Cezar Andrei
+ : @project Zorba/IO/Process
+ :
+ :)
+module namespace process = "http://www.zorba-xquery.com/modules/process";
+
+declare namespace an = "http://www.zorba-xquery.com/annotations";
+
+declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
+declare option ver:module-version "1.0";
+
+(:~
+ : Executes the specified string command in a separate process.
+ : This function does not allow arguments to be passed to
+ : the command.
+ :
+ : @param $cmd command to be executed (without arguments)
+ :
+ : @return the result of the execution as an element as
+ : shown in the documentation of this module. The exit-code
+ : element returns the exit code of the child process.
+ : For POSIX compliant platforms: returns the process exit code. If process is
+ : terminated or stopped: 128 + termination signal code.
+ : For Windows platforms: returns the return value of the process or the exit
+ : or terminate process specified value.
+ :
+ : @error process:PROC01 if an error occurred while communicating
+ : with the executed process.
+ :)
+declare %an:sequential function process:exec(
+ $cmd as xs:string
+) as element(process:result) external;
+
+(:~
+ : Executes the specified string command in a separate process.
+ : Each of the strings in the sequence passed in as the second
+ : argument is passed as an argument to the executed command.
+ :
+ : @param $cmd command to be executed (without arguments)
+ : @param $args the arguments passed to the executed command (e.g. "-la")
+ :
+ : @return the result of the execution as an element as
+ : shown in the documentation of this module. The exit-code
+ : element returns the exit code of the child process.
+ : For POSIX compliant platforms: returns the process exit code. If process is
+ : terminated or stopped: 128 + termination signal code.
+ : For Windows platforms: returns the return value of the process or the exit
+ : or terminate process specified value.
+ :
+ : @error process:PROC01 if an error occurred while communicating
+ : with the executed process.
+ :)
+declare %an:sequential function process:exec(
+ $cmd as xs:string,
+ $args as xs:string*
+) as element(process:result) external;
=== added directory 'src/process-1.xq.src'
=== added file 'src/process-1.xq.src/process.cpp'
--- src/process-1.xq.src/process.cpp 1970-01-01 00:00:00 +0000
+++ src/process-1.xq.src/process.cpp 2013-06-27 14:44:39 +0000
@@ -0,0 +1,577 @@
+/*
+ * Copyright 2006-2008 The FLWOR Foundation.
+ *
+ * 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 <sstream>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <vector>
+#include <iostream>
+#include <limits.h>
+#include <algorithm>
+
+#ifdef WIN32
+# include <windows.h>
+
+# ifndef NDEBUG
+# define _CRTDBG_MAP_ALLOC
+# include <stdlib.h>
+# include <crtdbg.h>
+# endif
+#else
+# include <unistd.h>
+# ifdef __APPLE__
+# include <sys/wait.h>
+# else
+# include <wait.h>
+# endif
+#endif
+
+#include <zorba/item_factory.h>
+#include <zorba/singleton_item_sequence.h>
+#include <zorba/diagnostic_list.h>
+#include <zorba/user_exception.h>
+#include <zorba/empty_sequence.h>
+
+#include "process.h"
+
+namespace zorba {
+namespace processmodule {
+
+/******************************************************************************
+ *****************************************************************************/
+void create_result_node(
+ zorba::Item& aResult,
+ const std::string& aStandardOut,
+ const std::string& aErrorOut,
+ int aExitCode,
+ zorba::ItemFactory* aFactory)
+{
+ zorba::Item lResultQName =
+ aFactory->createQName("http://www.zorba-xquery.com/modules/process", "result");
+ zorba::Item lExitCodeQName =
+ aFactory->createQName("http://www.zorba-xquery.com/modules/process", "exit-code");
+ zorba::Item lOutputQName =
+ aFactory->createQName("http://www.zorba-xquery.com/modules/process", "stdout");
+ zorba::Item lErrorQName =
+ aFactory->createQName("http://www.zorba-xquery.com/modules/process", "stderr");
+ zorba::Item lNullItem;
+ zorba::Item lTypeName =
+ aFactory->createQName("http://www.w3.org/2001/XMLSchema", "untyped");
+
+ zorba::NsBindings lNSBindings;
+
+ // root node called result
+ aResult = aFactory->createElementNode(
+ lNullItem, lResultQName, lTypeName, false, false, lNSBindings);
+
+ // <result><output> aStandardOut </output></result>
+ zorba::Item lOutput;
+ lOutput = aFactory->createElementNode(
+ aResult, lOutputQName, lTypeName, true, false, lNSBindings);
+ aFactory->createTextNode(lOutput, aStandardOut);
+
+ // <result><error> aErrorOut </error></result>
+ zorba::Item lError;
+ lError = aFactory->createElementNode(
+ aResult, lErrorQName, lTypeName, true, false, lNSBindings);
+ aFactory->createTextNode(lError, aErrorOut);
+
+ // <result><exit-code> aExitCode </exit-code></result>
+ zorba::Item lExitCode;
+ lExitCode = aFactory->createElementNode(
+ aResult, lExitCodeQName, lTypeName, true, false, lNSBindings);
+ std::ostringstream lExitCodeString;
+ lExitCodeString << aExitCode;
+ aFactory->createTextNode(lExitCode, lExitCodeString.str());
+}
+
+#ifdef WIN32
+
+/***********************************************
+* throw a descriptive message of the last error
+* accessible with GetLastError() on windows
+*/
+void throw_last_error(const zorba::String& aFilename, unsigned int aLineNumber){
+ LPVOID lpvMessageBuffer;
+ TCHAR lErrorBuffer[512];
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL, GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR)&lpvMessageBuffer, 0, NULL);
+ wsprintf(lErrorBuffer,TEXT("Process Error Code: %d - Message= %s"),GetLastError(), (TCHAR *)lpvMessageBuffer);
+ LocalFree(lpvMessageBuffer);
+ Item lQName = ProcessModule::getItemFactory()->createQName(
+ "http://www.zorba-xquery.com/modules/process",
+ "PROC01");
+#ifdef UNICODE
+ char error_str[1024];
+ WideCharToMultiByte(CP_UTF8, 0, lErrorBuffer, -1, error_str, sizeof(error_str), NULL, NULL);
+ throw USER_EXCEPTION(lQName, error_str);
+#else
+ throw USER_EXCEPTION(lQName, lErrorBuffer);
+#endif
+}
+
+/******************************************
+* read output from child process on windows
+*/
+void read_child_output(HANDLE aOutputPipe, std::ostringstream& aTargetStream)
+{
+ CHAR lBuffer[256];
+ DWORD lBytesRead;
+
+ while(TRUE)
+ {
+ if (
+ !ReadFile(aOutputPipe,lBuffer,sizeof(lBuffer),&lBytesRead,NULL)
+ || !lBytesRead
+ )
+ {
+ if (GetLastError() == ERROR_BROKEN_PIPE)
+ break; // finished
+ else{
+
+ // couldn't read from pipe
+ throw_last_error(__FILE__, __LINE__);
+ }
+ }
+
+ // remove the windows specific carriage return outputs
+ // std::stringstream lTmp;
+ // lTmp.write(lBuffer,lBytesRead);
+ // std::string lRawString=lTmp.str();
+ // std::replace( lRawString.begin(), lRawString.end(), '\r', ' ' );
+ // aTargetStream.write(lRawString.c_str(),static_cast<std::streamsize>(lRawString.length()));
+ for(DWORD i=0;i<lBytesRead;i++)
+ {
+ if(lBuffer[i] != '\r')
+ aTargetStream << lBuffer[i];
+ }
+ lBytesRead = 0;
+ }
+}
+
+/******************************************
+* Create a child process on windows with
+* redirected output
+*/
+BOOL create_child_process(HANDLE aStdOutputPipe,HANDLE aStdErrorPipe,const std::string& aCommand,PROCESS_INFORMATION& aProcessInformation){
+ STARTUPINFO lChildStartupInfo;
+ BOOL result=FALSE;
+
+ // set the output handles
+ FillMemory(&lChildStartupInfo,sizeof(lChildStartupInfo),0);
+ lChildStartupInfo.cb = sizeof(lChildStartupInfo);
+ GetStartupInfo(&lChildStartupInfo);
+ lChildStartupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
+ lChildStartupInfo.wShowWindow = SW_HIDE; // don't show the command window
+ lChildStartupInfo.hStdOutput = aStdOutputPipe;
+ lChildStartupInfo.hStdError = aStdErrorPipe;
+
+ // convert from const char* to char*
+ size_t length = strlen(aCommand.c_str());
+#ifdef UNICODE
+ WCHAR *tmpCommand = new WCHAR[length+1];
+ MultiByteToWideChar(CP_UTF8, 0, aCommand.c_str(), -1, tmpCommand, length+1);
+#else
+ char *tmpCommand=new char[length+1];
+ strcpy (tmpCommand,aCommand.c_str());
+ tmpCommand[length]='\0';
+#endif
+
+ try{
+
+ // settings for the child process
+ LPCTSTR lApplicationName=NULL;
+ LPTSTR lCommandLine=tmpCommand;
+ LPSECURITY_ATTRIBUTES lProcessAttributes=NULL;
+ LPSECURITY_ATTRIBUTES lThreadAttributes=NULL;
+ BOOL lInheritHandles=TRUE; // that's what we want
+ DWORD lCreationFlags=CREATE_NEW_CONSOLE;
+ LPVOID lEnvironment=NULL;
+ LPCTSTR lCurrentDirectory=NULL; // same as main process
+
+ // start child
+ result=CreateProcess(
+ lApplicationName,lCommandLine,lProcessAttributes,
+ lThreadAttributes,lInheritHandles,lCreationFlags,
+ lEnvironment,lCurrentDirectory,&lChildStartupInfo,
+ &aProcessInformation);
+
+ }catch(...){
+ delete[] tmpCommand;
+ tmpCommand=0;
+ throw;
+ }
+
+ delete[] tmpCommand;
+ tmpCommand=0;
+
+ return result;
+}
+
+/******************************************
+* run a process that executes the aCommand
+* in a new console and reads the output
+*/
+int run_process(
+ const std::string& aCommand,
+ std::ostringstream& aTargetOutStream,
+ std::ostringstream& aTargetErrStream)
+{
+ HANDLE lOutRead, lErrRead, lStdOut, lStdErr;
+ SECURITY_ATTRIBUTES lSecurityAttributes;
+ PROCESS_INFORMATION lChildProcessInfo;
+ DWORD exitCode=0;
+
+ // prepare security attributes
+ lSecurityAttributes.nLength= sizeof(lSecurityAttributes);
+ lSecurityAttributes.lpSecurityDescriptor = NULL;
+ lSecurityAttributes.bInheritHandle = TRUE;
+
+ // create output pipes
+ if(
+ !CreatePipe(&lOutRead,&lStdOut,&lSecurityAttributes,1024*1024) // std::cout >> lOutRead
+ || !CreatePipe(&lErrRead,&lStdErr,&lSecurityAttributes,1024*1024) // std::cerr >> lErrRead
+ ){
+ Item lQName = ProcessModule::getItemFactory()->createQName(
+ "http://www.zorba-xquery.com/modules/process", "PROC01");
+ throw USER_EXCEPTION(lQName,
+ "Couldn't create one of std::cout/std::cerr pipe for child process execution."
+ );
+ };
+
+ //start child process
+ BOOL ok = create_child_process(lStdOut,lStdErr,aCommand,lChildProcessInfo);
+ if(ok==TRUE)
+ {
+
+ // close unneeded handle
+ CloseHandle(lChildProcessInfo.hThread);
+
+ // wait for the process to finish
+ WaitForSingleObject(lChildProcessInfo.hProcess,INFINITE);
+ if (!GetExitCodeProcess(lChildProcessInfo.hProcess, &exitCode))
+ {
+ std::stringstream lErrorMsg;
+ lErrorMsg
+ << "Couldn't get exit code from child process. Executed command: '" << aCommand << "'.";
+ Item lQName = ProcessModule::getItemFactory()->createQName(
+ "http://www.zorba-xquery.com/modules/process", "PROC01");
+ throw USER_EXCEPTION(lQName, lErrorMsg.str().c_str());
+ }
+
+ CloseHandle(lChildProcessInfo.hProcess);
+ CloseHandle(lStdOut);
+ CloseHandle(lStdErr);
+
+ // read child's output
+ read_child_output(lOutRead,aTargetOutStream);
+ read_child_output(lErrRead,aTargetErrStream);
+
+ // close
+ CloseHandle(lOutRead);
+ CloseHandle(lErrRead);
+
+ }else{
+ CloseHandle(lStdOut);
+ CloseHandle(lStdErr);
+ CloseHandle(lOutRead);
+ CloseHandle(lErrRead);
+
+ // couldn't launch process
+ throw_last_error(__FILE__, __LINE__);
+ };
+
+
+ return exitCode;
+}
+
+#else
+
+#define READ 0
+#define WRITE 1
+
+pid_t zorba_popen(const char *command, int *infp, int *outfp, int *errfp)
+{
+ int p_stdin[2];
+ int p_stdout[2];
+ int p_stderr[2];
+ pid_t pid;
+
+ if (pipe(p_stdin) != 0 || pipe(p_stdout) != 0 || pipe(p_stderr) != 0)
+ return -1;
+
+ pid = fork();
+
+ if (pid < 0)
+ return pid;
+ else if (pid == 0)
+ {
+ close(p_stdin[WRITE]);
+ dup2(p_stdin[READ], 0); // duplicate stdin
+
+ close(p_stdout[READ]);
+ dup2(p_stdout[WRITE], 1); // duplicate stdout
+
+ close(p_stderr[READ]);
+ dup2(p_stderr[WRITE], 2); // duplicate stderr
+
+ execl("/bin/sh", "sh", "-c", command, NULL);
+ perror("execl"); // output the result to standard error
+ exit(errno);
+ }
+
+ if (infp == NULL)
+ close(p_stdin[WRITE]);
+ else
+ *infp = p_stdin[WRITE];
+
+ if (outfp == NULL)
+ close(p_stdout[READ]);
+ else
+ *outfp = p_stdout[READ];
+
+ if (errfp == NULL)
+ close(p_stderr[READ]);
+ else
+ *errfp = p_stderr[READ];
+
+ close(p_stdin[READ]); // We only write to the forks stdin anyway
+ close(p_stdout[WRITE]); // and we only read from its stdout
+ close(p_stderr[WRITE]); // and we only read from its stderr
+
+ return pid;
+}
+#endif
+
+/******************************************************************************
+ *****************************************************************************/
+zorba::ItemSequence_t
+ExecFunction::evaluate(
+ const Arguments_t& aArgs,
+ const zorba::StaticContext* aSctx,
+ const zorba::DynamicContext* aDctx) const
+{
+ std::string lCommand;
+ std::vector<std::string> lArgs;
+ int exit_code = 0;
+
+ lCommand = getOneStringArgument(aArgs, 0).c_str();
+
+ if (aArgs.size() > 1)
+ {
+ zorba::Item lArg;
+ Iterator_t arg1_iter = aArgs[1]->getIterator();
+ arg1_iter->open();
+ while (arg1_iter->next(lArg))
+ {
+ lArgs.push_back(lArg.getStringValue().c_str());
+ }
+ arg1_iter->close();
+ }
+
+ std::ostringstream lTmp;
+
+#ifdef WIN32
+ // execute process command in a new commandline
+ // with quotes at the beggining and at the end
+ lTmp << "cmd /C \"";
+#endif
+
+ lTmp << "\"" << lCommand << "\""; //quoted for spaced paths/filenames
+ size_t pos=0;
+ for (std::vector<std::string>::const_iterator lIter = lArgs.begin();
+ lIter != lArgs.end(); ++lIter)
+ {
+ pos = (*lIter).rfind('\\')+(*lIter).rfind('/');
+ if (int(pos)>=0)
+ lTmp << " \"" << *lIter << "\"";
+ else
+ lTmp << " " << *lIter;
+ }
+#ifdef WIN32
+ lTmp << "\""; // with quotes at the end for commandline
+#endif
+
+ std::ostringstream lStdout;
+ std::ostringstream lStderr;
+
+#ifdef WIN32
+ std::string lCommandLineString = lTmp.str();
+ int code = run_process(lCommandLineString, lStdout, lStderr);
+
+ if (code != 0)
+ {
+ std::stringstream lErrorMsg;
+ lErrorMsg << "Failed to execute the command (" << code << ")";
+ Item lQName = ProcessModule::getItemFactory()->createQName(
+ "http://www.zorba-xquery.com/modules/process", "PROC01");
+ throw USER_EXCEPTION(lQName, lErrorMsg.str().c_str());
+ }
+ exit_code = code;
+
+#else //not WIN32
+
+ int outfp;
+ int errfp;
+ int status;
+ pid_t pid;
+
+ pid = zorba_popen(lTmp.str().c_str(), NULL, &outfp, &errfp);
+ if ( pid == -1 )
+ {
+ std::stringstream lErrorMsg;
+ lErrorMsg << "Failed to execute the command (" << pid << ")";
+ Item lQName = ProcessModule::getItemFactory()->createQName(
+ "http://www.zorba-xquery.com/modules/process", "PROC01");
+ throw USER_EXCEPTION(lQName, lErrorMsg.str().c_str());
+ }
+ else
+ {
+ char lBuf[PATH_MAX];
+ ssize_t length = 0;
+ while ( (length=read(outfp, lBuf, PATH_MAX)) > 0 )
+ {
+ lStdout.write(lBuf, length);
+ }
+
+ status = close(outfp);
+
+ while ( (length=read(errfp, lBuf, PATH_MAX)) > 0 )
+ {
+ lStderr.write(lBuf, length);
+ }
+
+ status = close(errfp);
+
+ if ( status < 0 )
+ {
+ std::stringstream lErrorMsg;
+ lErrorMsg << "Failed to close the err stream (" << status << ")";
+ Item lQName = ProcessModule::getItemFactory()->createQName(
+ "http://www.zorba-xquery.com/modules/process", "PROC01");
+ throw USER_EXCEPTION(lQName, lErrorMsg.str().c_str());
+ }
+
+ int stat = 0;
+
+ pid_t w = waitpid(pid, &stat, 0);
+
+ if (w == -1)
+ {
+ std::stringstream lErrorMsg;
+ lErrorMsg << "Failed to wait for child process ";
+ Item lQName = ProcessModule::getItemFactory()->createQName(
+ "http://www.zorba-xquery.com/modules/process", "PROC01");
+ throw USER_EXCEPTION(lQName, lErrorMsg.str().c_str());
+ }
+
+ if (WIFEXITED(stat))
+ {
+ //std::cout << " WEXITSTATUS : " << WEXITSTATUS(stat) << std::endl; std::cout.flush();
+ exit_code = WEXITSTATUS(stat);
+ }
+ else if (WIFSIGNALED(stat))
+ {
+ //std::cout << " WTERMSIG : " << WTERMSIG(stat) << std::endl; std::cout.flush();
+ exit_code = 128 + WTERMSIG(stat);
+ }
+ else if (WIFSTOPPED(stat))
+ {
+ //std::cout << " STOPSIG : " << WSTOPSIG(stat) << std::endl; std::cout.flush();
+ exit_code = 128 + WSTOPSIG(stat);
+ }
+ else
+ {
+ //std::cout << " else : " << std::endl; std::cout.flush();
+ exit_code = 255;
+ }
+
+ //std::cout << " exit_code : " << exit_code << std::endl; std::cout.flush();
+
+ }
+#endif // WIN32
+
+ zorba::Item lResult;
+ create_result_node(lResult, lStdout.str(), lStderr.str(), exit_code,
+ theModule->getItemFactory());
+
+ return zorba::ItemSequence_t(new zorba::SingletonItemSequence(lResult));
+}
+
+String ExecFunction::getOneStringArgument (const Arguments_t& aArgs, int aPos)
+ const
+{
+ Item lItem;
+ Iterator_t args_iter = aArgs[aPos]->getIterator();
+ args_iter->open();
+ args_iter->next(lItem);
+ zorba::String lTmpString = lItem.getStringValue();
+ args_iter->close();
+ return lTmpString;
+}
+
+/******************************************************************************
+ *****************************************************************************/
+ProcessModule::~ProcessModule()
+{
+ for (FuncMap_t::const_iterator lIter = theFunctions.begin();
+ lIter != theFunctions.end(); ++lIter) {
+ delete lIter->second;
+ }
+ theFunctions.clear();
+}
+
+zorba::ExternalFunction*
+ProcessModule::getExternalFunction(const zorba::String& aLocalname)
+{
+ FuncMap_t::const_iterator lFind = theFunctions.find(aLocalname);
+ zorba::ExternalFunction*& lFunc = theFunctions[aLocalname];
+ if (lFind == theFunctions.end())
+ {
+ if (!aLocalname.compare("exec"))
+ {
+ lFunc = new ExecFunction(this);
+ }
+ }
+ return lFunc;
+}
+
+void ProcessModule::destroy()
+{
+ if (!dynamic_cast<ProcessModule*>(this)) {
+ return;
+ }
+ delete this;
+}
+
+ItemFactory* ProcessModule::theFactory = 0;
+
+} /* namespace processmodule */
+} /* namespace zorba */
+
+#ifdef WIN32
+# define DLL_EXPORT __declspec(dllexport)
+#else
+# define DLL_EXPORT __attribute__ ((visibility("default")))
+#endif
+
+extern "C" DLL_EXPORT zorba::ExternalModule* createModule() {
+ return new zorba::processmodule::ProcessModule();
+}
+/* vim:set et sw=2 ts=2: */
=== added file 'src/process-1.xq.src/process.h'
--- src/process-1.xq.src/process.h 1970-01-01 00:00:00 +0000
+++ src/process-1.xq.src/process.h 2013-06-27 14:44:39 +0000
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2006-2008 The FLWOR Foundation.
+ *
+ * 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.
+ */
+#ifndef ZORBA_PROCESSMODULE_PROCESS_H
+#define ZORBA_PROCESSMODULE_PROCESS_H
+
+#include <map>
+
+#include <zorba/zorba.h>
+#include <zorba/error.h>
+#include <zorba/external_module.h>
+#include <zorba/function.h>
+
+namespace zorba {
+namespace processmodule {
+
+/******************************************************************************
+ *****************************************************************************/
+class ProcessModule : public ExternalModule
+{
+private:
+ static ItemFactory* theFactory;
+
+protected:
+ class ltstr
+ {
+ public:
+ bool operator()(const String& s1, const String& s2) const
+ {
+ return s1.compare(s2) < 0;
+ }
+ };
+
+ typedef std::map<String, ExternalFunction*, ltstr> FuncMap_t;
+
+ FuncMap_t theFunctions;
+
+public:
+ virtual ~ProcessModule();
+
+ virtual zorba::String getURI() const
+ { return "http://www.zorba-xquery.com/modules/process"; }
+
+ virtual zorba::ExternalFunction*
+ getExternalFunction(const zorba::String& aLocalname);
+
+ virtual void destroy();
+
+ static ItemFactory* getItemFactory()
+ {
+ if(!theFactory)
+ {
+ theFactory = Zorba::getInstance(0)->getItemFactory();
+ }
+
+ return theFactory;
+ }
+};
+
+/******************************************************************************
+ *****************************************************************************/
+class ExecFunction : public ContextualExternalFunction
+{
+public:
+ ExecFunction(const ProcessModule* aModule) : theModule(aModule) {}
+
+ virtual ~ExecFunction() {}
+
+ virtual zorba::String
+ getLocalName() const { return "exec"; }
+
+ virtual zorba::ItemSequence_t
+ evaluate(const Arguments_t&,
+ const zorba::StaticContext*,
+ const zorba::DynamicContext*) const;
+
+ virtual String getURI() const
+ {
+ return theModule->getURI();
+ }
+
+protected:
+ const ProcessModule* theModule;
+
+ String getOneStringArgument (const Arguments_t& aArgs, int index) const;
+};
+
+
+
+} /* namespace processmodule */
+} /* namespace zorba */
+
+#endif // ZORBA_PROCESSMODULE_PROCESS_H
=== added file 'src/process-2.xq'
--- src/process-2.xq 1970-01-01 00:00:00 +0000
+++ src/process-2.xq 2013-06-27 14:44:39 +0000
@@ -0,0 +1,197 @@
+jsoniq version "1.0";
+
+(:
+ : Copyright 2006-2013 The FLWOR Foundation.
+ :
+ : 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.
+:)
+
+(:~
+ : <p>
+ : This module provides functions to create a native process and return the result
+ : (i.e. exit code, result on standard out and error) of executing the given
+ : file or command.
+ : </p>
+ :
+ : <p>
+ : Example:
+ : <pre>
+ : import module namespace p = "http://zorba.io/modules/process";
+ : p:exec("ls")
+ : </pre>
+ : </p>
+ :
+ : <p>
+ : Potential result:
+ : <pre>
+ : {
+ : "exit-code": 0,
+ : "stdout": "myfile.txt",
+ : "stderr": ""
+ : }
+ : </pre>
+ : </p>
+ :
+ : <p>
+ : The <tt>exec-command</tt> set of functions allows execution of commands
+ : through the command line interpreter of the operating system, such as "sh"
+ : on Unix systems or "cmd.exe" on Windows.
+ : </p>
+ :
+ : <p>
+ : For POSIX compliant platforms the functions return 128 + termination signal
+ : code of the process as their exit-code.
+ : On Windows platforms, the exit-code is the return value of the process or the exit
+ : or terminate process specified value.
+ : </p>
+ :
+ : @author Cezar Andrei, Nicolae Brinza
+ : @project Zorba/Input Output/Process
+ :)
+module namespace p = "http://zorba.io/modules/process";
+
+declare namespace an = "http://www.zorba-xquery.com/annotations";
+
+declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
+declare option ver:module-version "1.0";
+
+
+(:~
+ : <p>
+ : Executes the specified program in a separate process.
+ : </p>
+ :
+ : <p>
+ : This function does not allow arguments to be passed to
+ : the command. The $filename parameter can contain the full path to the
+ : executable. On Unix systems, if the specified filename does not contain
+ : a slash "/", the function duplicates the actions of the shell in searching
+ : for an executable file. The file is sought in the colon-separated list of
+ : directory pathnames specified in the PATH environment variable. If this
+ : variable isn't defined, the path list defaults to the current directory
+ : followed by the list of directories returned by the operating system.
+ : </p>
+ :
+ : @param $filename the name of program to be executed
+ :
+ : @return the result of the execution as an object
+ :
+ : @error p:COMMUNICATION if an error occurred while communicating with the process.
+ :)
+declare %an:sequential function p:exec(
+ $filename as string
+) as object external;
+
+(:~
+ : <p>
+ : Executes the specified program in a separate process.
+ : </p>
+ :
+ : <p>
+ : The $filename parameter can contain the full path to the
+ : executable. On Unix systems, if the specified filename does not contain
+ : a slash "/", the function duplicates the actions of the shell in searching
+ : for an executable file. The file is sought in the colon-separated list of
+ : directory pathnames specified in the PATH environment variable. If this
+ : variable isn't defined, the path list defaults to the current directory
+ : followed by the list of directories returned by the operating system.
+ : The $args parameters will be passed to the executable file as arguments.
+ : </p>
+ :
+ : @param $filename the name of program to be executed
+ : @param $args arguments to be passed to the executable
+ :
+ : @return the result of the execution as an object
+ :
+ : @error p:COMMUNICATION if an error occurred while communicating with the process.
+ :)
+declare %an:sequential function p:exec(
+ $filename as string,
+ $args as string*
+) as object external;
+
+(:~
+ : <p>
+ : Executes the specified program in a separate process.
+ : </p>
+ :
+ : <p>
+ : The $filename parameter can contain the full path to the
+ : executable. On Unix systems, if the specified filename does not contain
+ : a slash "/", the function duplicates the actions of the shell in searching
+ : for an executable file. The file is sought in the colon-separated list of
+ : directory pathnames specified in the PATH environment variable. If this
+ : variable isn't defined, the path list defaults to the current directory
+ : followed by the list of directories returned by the operating system.
+ : </p>
+ :
+ : <p>
+ : The $env allows defining and passing environment variables to the target
+ : process. They should be in the form "ENVVAR=value" where "ENVVAR" is the
+ : name of the environment variable and "value' is the string value to set it to.
+ : </p>
+ :
+ : @param $filename the name of program to be executed
+ : @param $args arguments to be passed to the executable
+ : @param $env list of environment variables for the executable
+ :
+ : @return the result of the execution as an object
+ :
+ : @error p:COMMUNICATION if an error occurred while communicating with the process.
+ :)
+declare %an:sequential function p:exec(
+ $filename as string,
+ $args as string*,
+ $env as string*
+) as object external;
+
+(:~
+ : <p>
+ : Executes the specified string command in a separate process.
+ : </p>
+ :
+ : <p>
+ : This function does not allow arguments to be passed to the command.
+ : </p>
+ :
+ : @param $cmd command to be executed (without arguments)
+ :
+ : @return the result of the execution as an object
+ :
+ : @error p:COMMUNICATION if an error occurred while communicating with the process.
+ :)
+declare %an:sequential function p:exec-command(
+ $cmd as string
+) as object external;
+
+(:~
+ : <p>
+ : Executes the specified string command in a separate process.
+ : </p>
+ :
+ : <p>
+ : Each of the strings in the sequence passed in as the second
+ : argument is passed as an argument to the executed command.
+ : </p>
+ :
+ : @param $cmd command to be executed (without arguments)
+ : @param $args the arguments passed to the executed command (e.g. "-la")
+ :
+ : @return the result of the execution as an object
+ :
+ : @error p:COMMUNICATION if an error occurred while communicating with the process.
+ :)
+declare %an:sequential function p:exec-command(
+ $cmd as string,
+ $args as string*
+) as object external;
=== added directory 'src/process-2.xq.src'
=== added file 'src/process-2.xq.src/process.cpp'
--- src/process-2.xq.src/process.cpp 1970-01-01 00:00:00 +0000
+++ src/process-2.xq.src/process.cpp 2013-06-27 14:44:39 +0000
@@ -0,0 +1,626 @@
+/*
+ * Copyright 2006-2013 The FLWOR Foundation.
+ *
+ * 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 <sstream>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <vector>
+#include <iostream>
+#include <limits.h>
+#include <algorithm>
+
+#ifdef WIN32
+# include <windows.h>
+# ifndef NDEBUG
+# define _CRTDBG_MAP_ALLOC
+# include <stdlib.h>
+# include <crtdbg.h>
+# endif
+#else
+# include <unistd.h>
+# ifdef __APPLE__
+# include <sys/wait.h>
+# else
+# include <wait.h>
+# endif
+#endif
+
+#include <zorba/item_factory.h>
+#include <zorba/singleton_item_sequence.h>
+#include <zorba/diagnostic_list.h>
+#include <zorba/user_exception.h>
+#include <zorba/empty_sequence.h>
+
+#include "process.h"
+
+// Provde the execvpe() function since some platforms don't have it
+#ifndef WIN32
+int execvpe(const char *program, char **argv, char **envp)
+{
+ clearenv();
+ int i = 0;
+ while (envp[i] != NULL)
+ putenv(envp[i++]);
+
+ int rc = execvp(program, argv);
+ return rc;
+}
+#endif
+
+
+namespace zorba {
+namespace processmodule {
+
+/******************************************************************************
+ *****************************************************************************/
+void create_result_object(
+ zorba::Item& aResult,
+ const std::string& aStandardOut,
+ const std::string& aErrorOut,
+ int aExitCode,
+ zorba::ItemFactory* aFactory)
+{
+ std::vector<std::pair<zorba::Item,zorba::Item> > pairs;
+
+ pairs.push_back(std::pair<zorba::Item,zorba::Item>(aFactory->createString("exit-code"), aFactory->createInt(aExitCode)));
+ pairs.push_back(std::pair<zorba::Item,zorba::Item>(aFactory->createString("stdout"), aFactory->createString(aStandardOut)));
+ pairs.push_back(std::pair<zorba::Item,zorba::Item>(aFactory->createString("stderr"), aFactory->createString(aErrorOut)));
+
+ aResult = aFactory->createJSONObject(pairs);
+}
+
+void free_char_vector(std::vector<char*> argv)
+{
+ for (unsigned int i=0; i<argv.size(); i++)
+ free(argv[i]);
+}
+
+#ifdef WIN32
+
+/***********************************************
+* throw a descriptive message of the last error
+* accessible with GetLastError() on windows
+*/
+void throw_last_error(const zorba::String& aFilename, unsigned int aLineNumber){
+ LPVOID lpvMessageBuffer;
+ TCHAR lErrorBuffer[512];
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL, GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR)&lpvMessageBuffer, 0, NULL);
+ wsprintf(lErrorBuffer,TEXT("Process Error Code: %d - Message= %s"),GetLastError(), (TCHAR *)lpvMessageBuffer);
+ LocalFree(lpvMessageBuffer);
+ Item lQName = ProcessModule::getItemFactory()->createQName(
+ "http://zorba.io/modules/process", "COMMUNICATION");
+#ifdef UNICODE
+ char error_str[1024];
+ WideCharToMultiByte(CP_UTF8, 0, lErrorBuffer, -1, error_str, sizeof(error_str), NULL, NULL);
+ throw USER_EXCEPTION(lQName, error_str);
+#else
+ throw USER_EXCEPTION(lQName, lErrorBuffer);
+#endif
+}
+
+/******************************************
+* read output from child process on windows
+*/
+void read_child_output(HANDLE aOutputPipe, std::ostringstream& aTargetStream)
+{
+ CHAR lBuffer[256];
+ DWORD lBytesRead;
+
+ while(TRUE)
+ {
+ if (
+ !ReadFile(aOutputPipe,lBuffer,sizeof(lBuffer),&lBytesRead,NULL)
+ || !lBytesRead
+ )
+ {
+ if (GetLastError() == ERROR_BROKEN_PIPE)
+ break; // finished
+ else{
+
+ // couldn't read from pipe
+ throw_last_error(__FILE__, __LINE__);
+ }
+ }
+
+ // remove the windows specific carriage return outputs
+ // std::stringstream lTmp;
+ // lTmp.write(lBuffer,lBytesRead);
+ // std::string lRawString=lTmp.str();
+ // std::replace( lRawString.begin(), lRawString.end(), '\r', ' ' );
+ // aTargetStream.write(lRawString.c_str(),static_cast<std::streamsize>(lRawString.length()));
+ for(DWORD i=0;i<lBytesRead;i++)
+ {
+ if(lBuffer[i] != '\r')
+ aTargetStream << lBuffer[i];
+ }
+ lBytesRead = 0;
+ }
+}
+
+/******************************************
+* Create a child process on windows with
+* redirected output
+*/
+BOOL create_child_process(HANDLE aStdOutputPipe,HANDLE aStdErrorPipe,const std::string& aCommand,PROCESS_INFORMATION& aProcessInformation){
+ STARTUPINFO lChildStartupInfo;
+ BOOL result=FALSE;
+
+ // set the output handles
+ FillMemory(&lChildStartupInfo,sizeof(lChildStartupInfo),0);
+ lChildStartupInfo.cb = sizeof(lChildStartupInfo);
+ GetStartupInfo(&lChildStartupInfo);
+ lChildStartupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
+ lChildStartupInfo.wShowWindow = SW_HIDE; // don't show the command window
+ lChildStartupInfo.hStdOutput = aStdOutputPipe;
+ lChildStartupInfo.hStdError = aStdErrorPipe;
+
+ // convert from const char* to char*
+ size_t length = strlen(aCommand.c_str());
+#ifdef UNICODE
+ WCHAR *tmpCommand = new WCHAR[length+1];
+ MultiByteToWideChar(CP_UTF8, 0, aCommand.c_str(), -1, tmpCommand, length+1);
+#else
+ char *tmpCommand=new char[length+1];
+ strcpy (tmpCommand,aCommand.c_str());
+ tmpCommand[length]='\0';
+#endif
+
+ try{
+
+ // settings for the child process
+ LPCTSTR lApplicationName=NULL;
+ LPTSTR lCommandLine=tmpCommand;
+ LPSECURITY_ATTRIBUTES lProcessAttributes=NULL;
+ LPSECURITY_ATTRIBUTES lThreadAttributes=NULL;
+ BOOL lInheritHandles=TRUE; // that's what we want
+ DWORD lCreationFlags=CREATE_NEW_CONSOLE;
+ LPVOID lEnvironment=NULL;
+ LPCTSTR lCurrentDirectory=NULL; // same as main process
+
+ // start child
+ result=CreateProcess(
+ lApplicationName,lCommandLine,lProcessAttributes,
+ lThreadAttributes,lInheritHandles,lCreationFlags,
+ lEnvironment,lCurrentDirectory,&lChildStartupInfo,
+ &aProcessInformation);
+
+ }catch(...){
+ delete[] tmpCommand;
+ tmpCommand=0;
+ throw;
+ }
+
+ delete[] tmpCommand;
+ tmpCommand=0;
+
+ return result;
+}
+
+/******************************************
+* run a process that executes the aCommand
+* in a new console and reads the output
+*/
+int run_process(
+ const std::string& aCommand,
+ std::ostringstream& aTargetOutStream,
+ std::ostringstream& aTargetErrStream)
+{
+ HANDLE lOutRead, lErrRead, lStdOut, lStdErr;
+ SECURITY_ATTRIBUTES lSecurityAttributes;
+ PROCESS_INFORMATION lChildProcessInfo;
+ DWORD exitCode=0;
+
+ // prepare security attributes
+ lSecurityAttributes.nLength= sizeof(lSecurityAttributes);
+ lSecurityAttributes.lpSecurityDescriptor = NULL;
+ lSecurityAttributes.bInheritHandle = TRUE;
+
+ // create output pipes
+ if(
+ !CreatePipe(&lOutRead,&lStdOut,&lSecurityAttributes,1024*1024) // std::cout >> lOutRead
+ || !CreatePipe(&lErrRead,&lStdErr,&lSecurityAttributes,1024*1024) // std::cerr >> lErrRead
+ ){
+ Item lQName = ProcessModule::getItemFactory()->createQName(
+ "http://zorba.io/modules/process", "COMMUNICATION");
+ throw USER_EXCEPTION(lQName,
+ "Couldn't create one of std::cout/std::cerr pipe for child process execution."
+ );
+ };
+
+ //start child process
+ BOOL ok = create_child_process(lStdOut,lStdErr,aCommand,lChildProcessInfo);
+ if(ok==TRUE)
+ {
+
+ // close unneeded handle
+ CloseHandle(lChildProcessInfo.hThread);
+
+ // wait for the process to finish
+ WaitForSingleObject(lChildProcessInfo.hProcess,INFINITE);
+ if (!GetExitCodeProcess(lChildProcessInfo.hProcess, &exitCode))
+ {
+ std::stringstream lErrorMsg;
+ lErrorMsg
+ << "Couldn't get exit code from child process. Executed command: '" << aCommand << "'.";
+ Item lQName = ProcessModule::getItemFactory()->createQName(
+ "http://zorba.io/modules/process", "COMMUNICATION");
+ throw USER_EXCEPTION(lQName, lErrorMsg.str().c_str());
+ }
+
+ CloseHandle(lChildProcessInfo.hProcess);
+ CloseHandle(lStdOut);
+ CloseHandle(lStdErr);
+
+ // read child's output
+ read_child_output(lOutRead,aTargetOutStream);
+ read_child_output(lErrRead,aTargetErrStream);
+
+ // close
+ CloseHandle(lOutRead);
+ CloseHandle(lErrRead);
+
+ }else{
+ CloseHandle(lStdOut);
+ CloseHandle(lStdErr);
+ CloseHandle(lOutRead);
+ CloseHandle(lErrRead);
+
+ // couldn't launch process
+ throw_last_error(__FILE__, __LINE__);
+ };
+
+
+ return exitCode;
+}
+
+#else
+
+#define READ 0
+#define WRITE 1
+
+pid_t exec_helper(int *infp, int *outfp, int *errfp, const char *command, char* argv[], char* env[])
+{
+ int p_stdin[2];
+ int p_stdout[2];
+ int p_stderr[2];
+ pid_t pid;
+
+ if (pipe(p_stdin) != 0 || pipe(p_stdout) != 0 || pipe(p_stderr) != 0)
+ return -1;
+
+ pid = fork();
+
+ if (pid < 0)
+ return pid;
+ else if (pid == 0)
+ {
+ close(p_stdin[WRITE]);
+ dup2(p_stdin[READ], 0); // duplicate stdin
+
+ close(p_stdout[READ]);
+ dup2(p_stdout[WRITE], 1); // duplicate stdout
+
+ close(p_stderr[READ]);
+ dup2(p_stderr[WRITE], 2); // duplicate stderr
+
+ if (command)
+ execl("/bin/sh", "sh", "-c", command, NULL);
+ else if (env == NULL)
+ execvp(argv[0], argv);
+ else
+ execvpe(argv[0], argv, env);
+
+ perror("execl"); // output the result to standard error
+
+ // TODO:
+ // Currently, if the child process exits with an error, the following happens:
+ // -- exit(errno) is called
+ // -- static object destruction ocurrs
+ // -- Zorba store is destroyed in the child process and this leaks several URIs
+ // and prints error messages to stderr. An exception is thrown and this overwrites
+ // the exit code of the invoked process.
+ //
+ // Until a proper solution is found, the child fork() process will call abort(), which
+ // will not trigger static object destruction.
+
+ abort();
+ // exit(errno);
+ }
+
+ if (infp == NULL)
+ close(p_stdin[WRITE]);
+ else
+ *infp = p_stdin[WRITE];
+
+ if (outfp == NULL)
+ close(p_stdout[READ]);
+ else
+ *outfp = p_stdout[READ];
+
+ if (errfp == NULL)
+ close(p_stderr[READ]);
+ else
+ *errfp = p_stderr[READ];
+
+ close(p_stdin[READ]); // We only write to the forks stdin anyway
+ close(p_stdout[WRITE]); // and we only read from its stdout
+ close(p_stderr[WRITE]); // and we only read from its stderr
+
+ return pid;
+}
+
+#endif
+
+
+/******************************************************************************
+ *****************************************************************************/
+String ExecFunction::getOneStringArgument (const Arguments_t& aArgs, int aPos) const
+{
+ Item lItem;
+ Iterator_t args_iter = aArgs[aPos]->getIterator();
+ args_iter->open();
+ args_iter->next(lItem);
+ zorba::String lTmpString = lItem.getStringValue();
+ args_iter->close();
+ return lTmpString;
+}
+
+zorba::ItemSequence_t
+ExecFunction::evaluate(
+ const Arguments_t& aArgs,
+ const zorba::StaticContext* aSctx,
+ const zorba::DynamicContext* aDctx) const
+{
+ std::string lCommand;
+ std::vector<std::string> lArgs;
+ std::vector<std::string> lEnv;
+ int exit_code = 0;
+
+ lCommand = getOneStringArgument(aArgs, 0).c_str();
+
+ if (aArgs.size() > 1)
+ {
+ zorba::Item lArg;
+ Iterator_t arg1_iter = aArgs[1]->getIterator();
+ arg1_iter->open();
+ while (arg1_iter->next(lArg))
+ lArgs.push_back(lArg.getStringValue().c_str());
+ arg1_iter->close();
+ }
+
+ if (aArgs.size() > 2)
+ {
+ zorba::Item lArg;
+ Iterator_t arg1_iter = aArgs[2]->getIterator();
+ arg1_iter->open();
+ while (arg1_iter->next(lArg))
+ lEnv.push_back(lArg.getStringValue().c_str());
+ arg1_iter->close();
+ }
+
+ std::ostringstream lTmp;
+
+#ifdef WIN32
+ // execute process command in a new commandline
+ // with quotes at the beggining and at the end
+ lTmp << "cmd /C \"";
+#endif
+
+ lTmp << "\"" << lCommand << "\""; //quoted for spaced paths/filenames
+ size_t pos=0;
+ for (std::vector<std::string>::const_iterator lIter = lArgs.begin();
+ lIter != lArgs.end(); ++lIter)
+ {
+ pos = (*lIter).rfind('\\')+(*lIter).rfind('/');
+ if (int(pos)>=0)
+ lTmp << " \"" << *lIter << "\"";
+ else
+ lTmp << " " << *lIter;
+ }
+#ifdef WIN32
+ lTmp << "\""; // with quotes at the end for commandline
+#endif
+
+ std::ostringstream lStdout;
+ std::ostringstream lStderr;
+
+#ifdef WIN32
+ std::string lCommandLineString = lTmp.str();
+ int code = run_process(lCommandLineString, lStdout, lStderr);
+
+ if (code != 0)
+ {
+ std::stringstream lErrorMsg;
+ lErrorMsg << "Failed to execute the command (" << code << ")";
+ Item lQName = ProcessModule::getItemFactory()->createQName(
+ "http://zorba.io/modules/process", "COMMUNICATION");
+ throw USER_EXCEPTION(lQName, lErrorMsg.str().c_str());
+ }
+ exit_code = code;
+
+#else //not WIN32
+
+ int outfp;
+ int errfp;
+ int status;
+ pid_t pid;
+
+ std::vector<char*> argv(lArgs.size()+2, NULL);
+ std::vector<char*> env(lEnv.size()+1, NULL);
+
+ try
+ {
+ if (theIsExecProgram)
+ {
+ argv[0] = strdup(lCommand.c_str());
+ for (unsigned int i=0; i<lArgs.size(); i++)
+ argv[i+1] = strdup(lArgs[i].c_str());
+
+ for (unsigned int i=0; i<lEnv.size(); i++)
+ env[i] = strdup(lEnv[i].c_str());
+
+ pid = exec_helper(NULL, &outfp, &errfp, NULL, argv.data(), lEnv.size() ? env.data() : NULL);
+ }
+ else
+ {
+ pid = exec_helper(NULL, &outfp, &errfp, lTmp.str().c_str(), argv.data(), NULL);
+ }
+
+ if ( pid == -1 )
+ {
+ std::stringstream lErrorMsg;
+ lErrorMsg << "Failed to execute the command (" << pid << ")";
+ Item lQName = ProcessModule::getItemFactory()->createQName(
+ "http://zorba.io/modules/process", "COMMUNICATION");
+ throw USER_EXCEPTION(lQName, lErrorMsg.str().c_str());
+ return NULL;
+ }
+
+ char lBuf[PATH_MAX];
+ ssize_t length = 0;
+ while ( (length=read(outfp, lBuf, PATH_MAX)) > 0 )
+ {
+ lStdout.write(lBuf, length);
+ }
+
+ status = close(outfp);
+
+ while ( (length=read(errfp, lBuf, PATH_MAX)) > 0 )
+ {
+ lStderr.write(lBuf, length);
+ }
+
+ status = close(errfp);
+
+ if ( status < 0 )
+ {
+ std::stringstream lErrorMsg;
+ lErrorMsg << "Failed to close the err stream (" << status << ")";
+ Item lQName = ProcessModule::getItemFactory()->createQName(
+ "http://zorba.io/modules/process", "COMMUNICATION");
+ throw USER_EXCEPTION(lQName, lErrorMsg.str().c_str());
+ }
+
+ int stat = 0;
+
+ pid_t w = waitpid(pid, &stat, 0);
+
+ if (w == -1)
+ {
+ std::stringstream lErrorMsg;
+ lErrorMsg << "Failed to wait for child process ";
+ Item lQName = ProcessModule::getItemFactory()->createQName(
+ "http://zorba.io/modules/process", "COMMUNICATION");
+ throw USER_EXCEPTION(lQName, lErrorMsg.str().c_str());
+ }
+
+ if (WIFEXITED(stat))
+ {
+ //std::cout << " WEXITSTATUS : " << WEXITSTATUS(stat) << std::endl; std::cout.flush();
+ exit_code = WEXITSTATUS(stat);
+ }
+ else if (WIFSIGNALED(stat))
+ {
+ //std::cout << " WTERMSIG : " << WTERMSIG(stat) << std::endl; std::cout.flush();
+ exit_code = 128 + WTERMSIG(stat);
+ }
+ else if (WIFSTOPPED(stat))
+ {
+ //std::cout << " STOPSIG : " << WSTOPSIG(stat) << std::endl; std::cout.flush();
+ exit_code = 128 + WSTOPSIG(stat);
+ }
+ else
+ {
+ //std::cout << " else : " << std::endl; std::cout.flush();
+ exit_code = 255;
+ }
+
+ //std::cout << " exit_code : " << exit_code << std::endl; std::cout.flush();
+ free_char_vector(argv);
+ free_char_vector(env);
+ }
+ catch (...)
+ {
+ free_char_vector(argv);
+ free_char_vector(env);
+ throw;
+ }
+#endif // WIN32
+
+ zorba::Item lResult;
+ create_result_object(lResult, lStdout.str(), lStderr.str(), exit_code,
+ theModule->getItemFactory());
+ return zorba::ItemSequence_t(new zorba::SingletonItemSequence(lResult));
+}
+
+
+/******************************************************************************
+ *****************************************************************************/
+ProcessModule::~ProcessModule()
+{
+ for (FuncMap_t::const_iterator lIter = theFunctions.begin();
+ lIter != theFunctions.end(); ++lIter) {
+ delete lIter->second;
+ }
+ theFunctions.clear();
+}
+
+zorba::ExternalFunction*
+ProcessModule::getExternalFunction(const zorba::String& aLocalname)
+{
+ FuncMap_t::const_iterator lFind = theFunctions.find(aLocalname);
+ zorba::ExternalFunction*& lFunc = theFunctions[aLocalname];
+ if (lFind == theFunctions.end())
+ {
+ if (aLocalname.compare("exec-command") == 0)
+ {
+ lFunc = new ExecFunction(this);
+ }
+ else if (aLocalname.compare("exec") == 0)
+ {
+ lFunc = new ExecFunction(this, true);
+ }
+ }
+ return lFunc;
+}
+
+void ProcessModule::destroy()
+{
+ if (!dynamic_cast<ProcessModule*>(this)) {
+ return;
+ }
+ delete this;
+}
+
+ItemFactory* ProcessModule::theFactory = 0;
+
+} /* namespace processmodule */
+} /* namespace zorba */
+
+#ifdef WIN32
+# define DLL_EXPORT __declspec(dllexport)
+#else
+# define DLL_EXPORT __attribute__ ((visibility("default")))
+#endif
+
+extern "C" DLL_EXPORT zorba::ExternalModule* createModule() {
+ return new zorba::processmodule::ProcessModule();
+}
+/* vim:set et sw=2 ts=2: */
=== added file 'src/process-2.xq.src/process.h'
--- src/process-2.xq.src/process.h 1970-01-01 00:00:00 +0000
+++ src/process-2.xq.src/process.h 2013-06-27 14:44:39 +0000
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2006-2013 The FLWOR Foundation.
+ *
+ * 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.
+ */
+#ifndef ZORBA_PROCESSMODULE_PROCESS_H
+#define ZORBA_PROCESSMODULE_PROCESS_H
+
+#include <map>
+
+#include <zorba/zorba.h>
+#include <zorba/error.h>
+#include <zorba/external_module.h>
+#include <zorba/function.h>
+
+namespace zorba {
+namespace processmodule {
+
+/******************************************************************************
+ *****************************************************************************/
+class ProcessModule : public ExternalModule
+{
+private:
+ static ItemFactory* theFactory;
+
+protected:
+ class ltstr
+ {
+ public:
+ bool operator()(const String& s1, const String& s2) const
+ {
+ return s1.compare(s2) < 0;
+ }
+ };
+
+ typedef std::map<String, ExternalFunction*, ltstr> FuncMap_t;
+
+ FuncMap_t theFunctions;
+
+public:
+ virtual ~ProcessModule();
+
+ virtual zorba::String getURI() const
+ { return "http://zorba.io/modules/process"; }
+
+ virtual zorba::ExternalFunction*
+ getExternalFunction(const zorba::String& aLocalname);
+
+ virtual void destroy();
+
+ static ItemFactory* getItemFactory()
+ {
+ if(!theFactory)
+ {
+ theFactory = Zorba::getInstance(0)->getItemFactory();
+ }
+
+ return theFactory;
+ }
+};
+
+/******************************************************************************
+ *****************************************************************************/
+class ExecFunction : public ContextualExternalFunction
+{
+public:
+ ExecFunction(const ProcessModule* aModule, bool aExecProgram = false)
+ : theModule(aModule), theIsExecProgram(aExecProgram) {}
+
+ virtual ~ExecFunction() {}
+
+ virtual zorba::String
+ getLocalName() const { if (theIsExecProgram) return "exec"; else return "exec-command"; }
+
+ virtual zorba::ItemSequence_t
+ evaluate(const Arguments_t&,
+ const zorba::StaticContext*,
+ const zorba::DynamicContext*) const;
+
+ virtual String getURI() const
+ {
+ return theModule->getURI();
+ }
+
+protected:
+ const ProcessModule* theModule;
+
+ bool theIsExecProgram; // if set to true, will use the execvpe() version of the system function
+ // if set to false, will build a command string and pass it to
+ // either "bash" or "cmd.exe" (through execl() on Linux)
+
+ String getOneStringArgument (const Arguments_t& aArgs, int index) const;
+};
+
+} /* namespace processmodule */
+} /* namespace zorba */
+
+#endif // ZORBA_PROCESSMODULE_PROCESS_H
=== added directory 'test'
=== renamed directory 'test' => 'test.moved'
=== added directory 'test/ExpQueryResults'
=== added file 'test/ExpQueryResults/process1-01.xml.res'
--- test/ExpQueryResults/process1-01.xml.res 1970-01-01 00:00:00 +0000
+++ test/ExpQueryResults/process1-01.xml.res 2013-06-27 14:44:39 +0000
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<result><out>hello world</out><err>Ooops. an error.</err></result>
=== added file 'test/ExpQueryResults/process2-01.xml.res'
--- test/ExpQueryResults/process2-01.xml.res 1970-01-01 00:00:00 +0000
+++ test/ExpQueryResults/process2-01.xml.res 2013-06-27 14:44:39 +0000
@@ -0,0 +1,1 @@
+true
\ No newline at end of file
=== added file 'test/ExpQueryResults/process2-02.xml.res'
--- test/ExpQueryResults/process2-02.xml.res 1970-01-01 00:00:00 +0000
+++ test/ExpQueryResults/process2-02.xml.res 2013-06-27 14:44:39 +0000
@@ -0,0 +1,1 @@
+true
\ No newline at end of file
=== added file 'test/ExpQueryResults/process2-03.xml.res'
--- test/ExpQueryResults/process2-03.xml.res 1970-01-01 00:00:00 +0000
+++ test/ExpQueryResults/process2-03.xml.res 2013-06-27 14:44:39 +0000
@@ -0,0 +1,1 @@
+true
\ No newline at end of file
=== added file 'test/ExpQueryResults/process2-04.xml.res'
--- test/ExpQueryResults/process2-04.xml.res 1970-01-01 00:00:00 +0000
+++ test/ExpQueryResults/process2-04.xml.res 2013-06-27 14:44:39 +0000
@@ -0,0 +1,1 @@
+true
\ No newline at end of file
=== added file 'test/ExpQueryResults/process2-05.xml.res'
--- test/ExpQueryResults/process2-05.xml.res 1970-01-01 00:00:00 +0000
+++ test/ExpQueryResults/process2-05.xml.res 2013-06-27 14:44:39 +0000
@@ -0,0 +1,1 @@
+true
\ No newline at end of file
=== added file 'test/ExpQueryResults/process2-06.xml.res'
--- test/ExpQueryResults/process2-06.xml.res 1970-01-01 00:00:00 +0000
+++ test/ExpQueryResults/process2-06.xml.res 2013-06-27 14:44:39 +0000
@@ -0,0 +1,1 @@
+true
\ No newline at end of file
=== added file 'test/ExpQueryResults/process2-07.xml.res'
--- test/ExpQueryResults/process2-07.xml.res 1970-01-01 00:00:00 +0000
+++ test/ExpQueryResults/process2-07.xml.res 2013-06-27 14:44:39 +0000
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<result><out>hello world</out><err>Ooops. an error.</err></result>
=== added directory 'test/Queries'
=== added file 'test/Queries/process1-01.xq'
--- test/Queries/process1-01.xq 1970-01-01 00:00:00 +0000
+++ test/Queries/process1-01.xq 2013-06-27 14:44:39 +0000
@@ -0,0 +1,24 @@
+import module namespace proc = "http://www.zorba-xquery.com/modules/process#1.0";
+
+
+{
+ variable $stdOutTest := proc:exec("echo","hello world") ;
+ variable $stdErrTest := proc:exec("echo","Ooops. an error. 1>&2");
+ variable $stdOutWinTest := proc:exec("cmd", ("/c", "echo","hello world")) ;
+ variable $stdErrWinTest := proc:exec("cmd", ("/c", "echo","Ooops. an error. 1>&2"));
+
+ let $result :=
+ <result>
+ <out>{normalize-space(data($stdOutTest/proc:stdout))}</out>
+ <err>{normalize-space(data($stdErrTest/proc:stderr))}</err>
+ </result>
+ return
+ if (contains($result/err/text(),"is not recognized as an internal or external command"))
+ then
+ <result>
+ <out>{normalize-space(data($stdOutWinTest/proc:stdout))}</out>
+ <err>{normalize-space(data($stdErrWinTest/proc:stderr))}</err>
+ </result>
+ else
+ $result
+}
=== added file 'test/Queries/process2-01.xq'
--- test/Queries/process2-01.xq 1970-01-01 00:00:00 +0000
+++ test/Queries/process2-01.xq 2013-06-27 14:44:39 +0000
@@ -0,0 +1,5 @@
+import module namespace proc = "http://zorba.io/modules/process";
+
+let $result := proc:exec("echo")
+return $result("stdout") eq "
+"
=== added file 'test/Queries/process2-02.xq'
--- test/Queries/process2-02.xq 1970-01-01 00:00:00 +0000
+++ test/Queries/process2-02.xq 2013-06-27 14:44:39 +0000
@@ -0,0 +1,5 @@
+import module namespace proc = "http://zorba.io/modules/process";
+
+let $result := proc:exec("echo",("hello","world"))
+return $result("stdout") eq "hello world
+"
=== added file 'test/Queries/process2-03.xq'
--- test/Queries/process2-03.xq 1970-01-01 00:00:00 +0000
+++ test/Queries/process2-03.xq 2013-06-27 14:44:39 +0000
@@ -0,0 +1,5 @@
+import module namespace proc = "http://zorba.io/modules/process";
+
+let $result := proc:exec("printenv",("TEST_ENV_VAR"),"TEST_ENV_VAR=foo")
+return $result("stdout") eq "foo
+"
=== added file 'test/Queries/process2-04.xq'
--- test/Queries/process2-04.xq 1970-01-01 00:00:00 +0000
+++ test/Queries/process2-04.xq 2013-06-27 14:44:39 +0000
@@ -0,0 +1,6 @@
+import module namespace proc = "http://zorba.io/modules/process";
+
+let $result := proc:exec("printenv",("TEST_ENV_VAR","VAR2"),("TEST_ENV_VAR=foo","VAR2=bar"))
+return $result("stdout") eq "foo
+bar
+"
=== added file 'test/Queries/process2-05.xq'
--- test/Queries/process2-05.xq 1970-01-01 00:00:00 +0000
+++ test/Queries/process2-05.xq 2013-06-27 14:44:39 +0000
@@ -0,0 +1,5 @@
+import module namespace proc = "http://zorba.io/modules/process";
+
+let $result := proc:exec("echo","{}[]()()''~!@#$%^&*_-+|<>/?,.")
+return $result("stdout") eq "{}[]()()''~!@#$%^&*_-+|<>/?,.
+"
=== added file 'test/Queries/process2-06.xq'
--- test/Queries/process2-06.xq 1970-01-01 00:00:00 +0000
+++ test/Queries/process2-06.xq 2013-06-27 14:44:39 +0000
@@ -0,0 +1,4 @@
+import module namespace proc = "http://zorba.io/modules/process";
+
+let $result := proc:exec("this_executable_does_not_exist")
+return $result("exit-code") ne 0
=== added file 'test/Queries/process2-07.xq'
--- test/Queries/process2-07.xq 1970-01-01 00:00:00 +0000
+++ test/Queries/process2-07.xq 2013-06-27 14:44:39 +0000
@@ -0,0 +1,24 @@
+import module namespace proc = "http://zorba.io/modules/process";
+
+
+{
+ variable $stdOutTest := proc:exec-command("echo","hello world") ;
+ variable $stdErrTest := proc:exec-command("echo","Ooops. an error. 1>&2");
+ variable $stdOutWinTest := proc:exec-command("cmd", ("/c", "echo","hello world")) ;
+ variable $stdErrWinTest := proc:exec-command("cmd", ("/c", "echo","Ooops. an error. 1>&2"));
+
+ let $result :=
+ <result>
+ <out>{normalize-space(data($stdOutTest("stdout")))}</out>
+ <err>{normalize-space(data($stdErrTest("stderr")))}</err>
+ </result>
+ return
+ if (contains($result/err/text(),"is not recognized as an internal or external command"))
+ then
+ <result>
+ <out>{normalize-space(data($stdOutWinTest("stdout")))}</out>
+ <err>{normalize-space(data($stdErrWinTest("stderr")))}</err>
+ </result>
+ else
+ $result
+}
Follow ups