ustrImageName - the file URL of the executable to be started. This can be NULL, in which case the file URL of the executable must be the first element in ustrArguments.
ustrArguments - an array of argument strings. Can be NULL if strImageName is not NULL. If, however, strImageName is NULL the function expects the first element of ustrArguments will contain the file URL of the executable to start.
nArguments - the number of arguments provided. If this number is 0 strArguments will be ignored.
Options - a combination of int-constants to describe the mode of execution.
Security - the user and the user rights under which the process is started. This may be NULL, in which case the process will be started in the context of the current user.
ustrDirectory - the file URL of the working directory of the new process. If the specified directory does not exist or is inaccessible the working directory of the newly created process is undefined. If this parameter is NULL or the caller provides an empty string the new process will have the same current working directory as the calling process.
ustrEnvironments - an array of strings describing environment variables that should be merged into the environment of the new process. Each string has to be in the form "variable=value". This parameter can be NULL in which case the new process gets the same environment as the parent process.
nEnvironmentVarsthe number of environment variables to set.
pProcess - an output parameter, this variable is a pointer to an oslProcess variable, which receives the handle of the newly created process. This parameter must not be NULL.
On both Windows and Unix platforms, this is a wrapper to osl_executeProcess_WithRedirectedIO().
Step 6: initializes the process ID (PID) as 0, sets up a condition variable (for more details on this, see the threads chapter), and sets the next process in the linked list to NULL.
Initializes the process ID (PID) as 0, sets up a condition variable (for more details on this, see the threads chapter), and sets the next process in the linked list to NULL.
This sets up a condition variable that is set if the thread is unexpected terminated.
Step 7:ChildListMutex ensures that the pointer to the global pointer to the mutex that protects access to the global linked list of child processes is set to a new mutex.
if (ChildListMutex ==nullptr) ChildListMutex =osl_createMutex();
Step 8: The process is actually executed in this thread, when it is done it sets the condition variable to allow the function to shutdown the process cleanly.
Note that it calls on osl_createThread(ChildStatusProc, &Data) - we fork and execute the process in ChildStatusProc(...) which I will detail later.
The process is actually executed in this thread, when it is done it sets the condition variable to allow the function to shutdown the process cleanly.
for (i =0; Data.m_pszArgs[i] !=nullptr; i++)free(const_cast<char*>(Data.m_pszArgs[i]));for (i =0; Data.m_pszEnv[i] !=nullptr; i++)free(Data.m_pszEnv[i]);if ( Data.m_pszDir !=nullptr ) {free(const_cast<char*>(Data.m_pszDir)); }osl_destroyThread(hThread);
Step 10: If the process has been flagged to wait, then this waits for the child process to finish (via osl_joinProcess(*pProcess), which blocks the current process until the specified process finishes).
Step 11: If the process was terminated abnormally the application cleans up by destroying the termination condition variable, and frees the process structure.
ChildStatusProc(...) forks and executes the process, sets up a Unix domain socket between the child and parent processes and redirects IO pipes. It works as follows:
It gives the current thread a name and declares the variables needed.
osl_setThreadName("osl_executeProcess");pid_t pid =-1;int status =0;int channel[2] = { -1,-1 }; ProcessData data; ProcessData *pdata;int stdOutput[2] = { -1,-1 }, stdInput[2] = { -1,-1 }, stdError[2] = { -1,-1 }; pdata = static_cast<ProcessData *>(pData); /* make a copy of our data, because forking will only copy our local stack of the thread, so the process data will not be accessible in our child process */memcpy(&data, pData,sizeof(data));
Step 2: create a Unix domain socket so that the parent and child processes can communicate. A Unix domain socket is part of the Unix address family (what the "AF" in "AF_UNIX" stands for), and uses a byte-oriented bi-directional stream. The socketpair(...) function returns a pair of file descriptors that define both communication endpoints. These file descriptors are set to close on execution termination with fcntl(...) by setting FD_CLOEXEC.
Step 4:fork the process. What this means is that the process is cloned with a new process ID, and the cloned process is made the child of the process that forked it.
if ((status ==0) && ((pid =fork()) ==0)) {
Child process: Step 1: if fork(...) returns 0 then the process is the child process.
A copy of the file descriptors of the parent process is provided to the child process, which means that the child process needs to close the file descriptor used for the parent process of the Unix domain socket used for IPC between the child and parent processes. This needs to be done because until all file descriptors referencing this socket are closed the resource will not be freed.
Child process: Step 2: if there is a valid process user owner (data.m_uid is not -1) and the process owner or the process group of the child process are not the same as the parent's, then the process's uid and guid is changed to the one passed to the function. It also clears the $HOME environment variable.
if ((data.m_uid != (uid_t)-1) && ((data.m_uid !=getuid()) || (data.m_gid !=getgid()))) {OSL_ASSERT(geteuid() ==0); /* must be root */if (!INIT_GROUPS(data.m_name,data.m_gid)|| (setuid(data.m_uid)!=0))SAL_WARN("sal.osl","Failed to change uid and guid, errno="<< errno <<" ("<< strerror(errno) <<")" );const rtl::OUString envVar("HOME");osl_clearEnvironment(envVar.pData); }
Child process: Step 3: change the working director of the process
if (data.m_pszDir) chstatus =chdir(data.m_pszDir);
Child process: Step 4: if allowed, then checks for invalid environment variables, closes the write end of the standard input descriptor and the read end of standard output and standard error (as these do not get used in the child process and redirects the pipes created earlier to their corresponding pipe ends
if (chstatus ==0&& ((data.m_uid == (uid_t)-1) || ((data.m_uid ==getuid()) && (data.m_gid ==getgid())))) {int i;for (i =0; data.m_pszEnv[i] != nullptr; i++) {if (strchr(data.m_pszEnv[i],'=')== nullptr) {unsetenv(data.m_pszEnv[i]); /* TODO: check error return*/ }else {putenv(data.m_pszEnv[i]); /* TODO: check error return*/ } } /* Connect std IO to pipe ends */ /* Write end of stdInput not used in child process */if (stdInput[1] !=-1) close( stdInput[1] ); /* Read end of stdOutput not used in child process */if (stdOutput[0] !=-1) close( stdOutput[0] ); /* Read end of stdError not used in child process */if (stdError[0] !=-1) close( stdError[0] ); /* Redirect pipe ends to std IO */if ( stdInput[0] != STDIN_FILENO ) {dup2( stdInput[0], STDIN_FILENO );if (stdInput[0] !=-1) close( stdInput[0] ); }if ( stdOutput[1] != STDOUT_FILENO ) {dup2( stdOutput[1], STDOUT_FILENO );if (stdOutput[1] !=-1) close( stdOutput[1] ); }if ( stdError[1] != STDERR_FILENO ) {dup2( stdError[1], STDERR_FILENO );if (stdError[1] !=-1) close( stdError[1] ); }
Child process: Step 5: program now executes the program and passes on the arguments via execv(...).
// No need to check the return value of execv. If we return from// it, an error has occurred.execv(data.m_pszArgs[0], const_cast<char**>(data.m_pszArgs)); }
Child process: unreachable code: we can only get into this section if execv fails. If this occurs, then we send the error to the parent process to advise it of the problem.
SAL_WARN("sal.osl","Failed to exec, errno="<< errno <<" ("<<strerror(errno) <<")");SAL_WARN("sal.osl","ChildStatusProc : starting '"<< data.m_pszArgs[0] <<"' failed"); /* if we reach here, something went wrong */ errno_copy = errno;if ( !safeWrite(channel[1],&errno_copy,sizeof(errno_copy)) )SAL_WARN("sal.osl","sendFdPipe : sending failed ("<<strerror(errno) <<")");if ( channel[1] !=-1 )close(channel[1]);_exit(255); }
Parent process: Step 1:fork() returns a non-zero positive value that holds the child process' ID when in the parent process that called on fork(). If the value is -1 then this indicates an error and errno is set.
As with the child process closing the Unix domain socket's parent file descriptor, the parent process must close the child process' Unix domain socket file descriptor so it can later be reclaimed by the operating system.
The unused pipe ends of the parent process must also be closed for the same reason.
Parent process: Step 2: If the PID is less than 0, then it indicates an error. The parent process waits for the child process to send its error to the parent process, which it reads from the socket. If the read fails, then errno returns EINTR so it breaks out of the loop.
Parent process: Step 3: Once the child process has terminated, then close the IPC socket on the parent.
if (channel[0] !=-1) close(channel[0]);
Parent process: Step 4a: If the process finished cleanly, then lock the child list, record the process ID, add the process to the linked list of children, and store the pipe ends in ProcessData structure.
if ((pid >0) && (i ==0)) {pid_t child_pid;osl_acquireMutex(ChildListMutex);pdata->m_pProcImpl->m_pid = pid;pdata->m_pProcImpl->m_pnext = ChildList; ChildList =pdata->m_pProcImpl; /* Store used pipe ends in data structure */if ( pdata->m_pInputWrite )*(pdata->m_pInputWrite) =osl::detail::createFileHandleFromFD( stdInput[1] );if ( pdata->m_pOutputRead )*(pdata->m_pOutputRead) =osl::detail::createFileHandleFromFD( stdOutput[0] );if ( pdata->m_pErrorRead )*(pdata->m_pErrorRead) =osl::detail::createFileHandleFromFD( stdError[0] );osl_releaseMutex(ChildListMutex);
Notify threads that the process has finished starting.
osl_setCondition(pdata->m_started);
Now for final cleanup we need to run waitpid(...) on the child process.
do { child_pid =waitpid(pid,&status,0); } while (0> child_pid && EINTR == errno);if ( child_pid <0) {SAL_WARN("sal.osl","Failed to wait for child process, errno="<< errno <<" ("<< strerror(errno) <<")"); /* We got an other error than EINTR. Anyway we have to wake up the waiting thread under any circumstances */ child_pid = pid; }
Walk the child process list until it finds the child process that exited and check to see and store the process status - if the process exited normally (WIFEXITED(status)) then record this, otherwise if the process terminated abnormally (WIFSIGNALED(status)), in which case store the termination process status, or if it terminated for any other reason then store -1.
After the status has been recorded set the termination condition variable.
if (child_pid >0) { oslProcessImpl* pChild;osl_acquireMutex(ChildListMutex); pChild = ChildList; /* check if it is one of our child processes */while (pChild != nullptr) {if (pChild->m_pid == child_pid) {if (WIFEXITED(status))pChild->m_status =WEXITSTATUS(status);elseif (WIFSIGNALED(status))pChild->m_status =128+WTERMSIG(status);elsepChild->m_status =-1;osl_setCondition(pChild->m_terminated); } pChild =pChild->m_pnext; }osl_releaseMutex(ChildListMutex); } }
Parent process: Step 4b: If the process terminated abnormally the close the pipe ends, and if for some reason the child process was actually created, to prevent the process from being a "defunct" process wait on the process, then once this is done set the condition variable to notify the parent thread.
else {SAL_WARN("sal.osl","ChildStatusProc : starting '"<<data.m_pszArgs[0] <<"' failed");SAL_WARN("sal.osl","Failed to launch child process, child reports errno="<< status <<" ("<< strerror(status) <<")"); /* Close pipe ends */if (pdata->m_pInputWrite)*pdata->m_pInputWrite = nullptr;if (pdata->m_pOutputRead)*pdata->m_pOutputRead = nullptr;if (pdata->m_pErrorRead)*pdata->m_pErrorRead = nullptr;if (stdInput[1] !=-1) close( stdInput[1] );if (stdOutput[0] !=-1) close( stdOutput[0] );if (stdError[0] !=-1) close( stdError[0] );// if pid > 0 then a process was created, even if it later failed// e.g. bash searching for a command to execute, and we still// need to clean it up to avoid "defunct" processesif (pid >0) {pid_t child_pid;do { child_pid =waitpid(pid,&status,0); } while (0> child_pid && EINTR == errno); } /* notify (and unblock) parent thread */osl_setCondition(pdata->m_started); } } }}
Windows implementation
osl_executeProcess(...) calls on osl_executeProcess_WithRedirectedIO(...), which on the Windows platforms works as follows:
Step 2: check if the executable is a batch file, if so add the "batch processor" (normally cmd.exe, which requires a /c switch) to the processor
DWORD flags = NORMAL_PRIORITY_CLASS; rtl::OUStringBuffer command_line;if (is_batch_file(exe_path)) { rtl::OUString batch_processor =get_batch_processor();if (batch_processor.getLength()) { /* cmd.exe does not work without a console window */if (!(Options & osl_Process_WAIT) || (Options & osl_Process_DETACHED)) flags |= CREATE_NEW_CONSOLE;command_line.append(batch_processor);command_line.append(" /c "); }else {// should we return here in case of error?return osl_Process_E_Unknown; } } command_line.append(exe_path);
Step 2: process the arguments from the command line
/* Add remaining arguments to command line. If ustrImageName is nullptr the first parameter is the name of the executable so we have to start at 1 instead of 0 */for (sal_uInt32 n = (nullptr != ustrImageName) ?0:1; n < nArguments; n++) {command_line.append(SPACE); /* Quote arguments containing blanks */if (rtl::OUString(ustrArguments[n]).indexOf(' ') !=-1)command_line.append(quote_string(ustrArguments[n]));elsecommand_line.append(ustrArguments[n]); }
When the process is created from the command processor, the command processor creates a new console process, which is a character mode application that has an input buffer and one or more screen buffers. When the command processor creates a new process, this new process inherits the command processors's console, unless the CreateProcess(...) function is passed a CREATE_NEW_CONSOLE flag (in which case, the new process creates a new process with a new console), or DETACHED_PROCESS (which creates a new process, that doesn't have a console process attached to it). These two flags are obviously incompatible, hence the check to see if CREATE_NEW_CONSOLE is set as a flag option.
if ((Options & osl_Process_DETACHED) &&!(flags & CREATE_NEW_CONSOLE)) flags |= DETACHED_PROCESS;
Allocate the initial STARTUPINFO instance (STARTUPINFO "specifies the window station, desktop, standard handles, and appearance of the main window for a process at creation time.")
Step 6: Creates the process, either as an impersonated user or as the current user. CreateProcess(...) takes the command line, startup info, process creation flags, environment and current working directory. It the returns the process information in a PROCESS_INFORMATION structure.
Step 7: Once the process has been created, then the handles need to be closed.
/* Now we can close the pipe ends that are used by the child process */if (hInputRead)CloseHandle(hInputRead);if (hOutputWrite)CloseHandle(hOutputWrite);if (hErrorWrite)CloseHandle(hErrorWrite);if (bRet) {CloseHandle(process_info.hThread); oslProcessImpl* pProcImpl = static_cast<oslProcessImpl*>(rtl_allocateMemory(sizeof(oslProcessImpl)));if (pProcImpl != nullptr) {pProcImpl->m_hProcess =process_info.hProcess;pProcImpl->m_IdProcess =process_info.dwProcessId;*pProcess = static_cast<oslProcess>(pProcImpl);if (Options & osl_Process_WAIT)WaitForSingleObject(pProcImpl->m_hProcess, INFINITE);if (pProcessInputWrite)*pProcessInputWrite =osl_createFileHandleFromOSHandle(hInputWrite, osl_File_OpenFlag_Write);if (pProcessOutputRead)*pProcessOutputRead =osl_createFileHandleFromOSHandle(hOutputRead, osl_File_OpenFlag_Read);if (pProcessErrorRead)*pProcessErrorRead =osl_createFileHandleFromOSHandle(hErrorRead, osl_File_OpenFlag_Read);return osl_Process_E_None; } } /* if an error occurred we have to close the server side pipe ends too */if (hInputWrite)CloseHandle(hInputWrite);if (hOutputRead)CloseHandle(hOutputRead);if (hErrorRead)CloseHandle(hErrorRead);return osl_Process_E_Unknown;}