| iMatix home page | << | < | > | >> |
![]() Version 1.91 |
#include "sflproc.h" PROCESS process_create ( const char *filename, /* Name of file to execute */ char *argv [], /* Arguments for process, or NULL */ const char *workdir, /* Working directory, or NULL */ const char *std_in, /* Stdin device, or NULL */ const char *std_out, /* Stdout device, or NULL */ const char *std_err, /* Stderr device, or NULL */ char *envv [], /* Environment variables, or NULL */ Bool wait /* Wait for process to end */ )
Creates a subprocess and returns a PROCESS identifying the new process. Optionally directs standard input, output, and error streams to specified devices. The caller can also specify environment symbols that the subprocess can access. Accepts these arguments:
filename | File to execute; if not fully specified, searches PATH. |
argv [] | List of arguments; argv [0] is filename; ends in a NULL. |
workdir | Working directory; if NULL, remains in current directory. |
std in | Device to use for standard input; NULL = no redirection. |
std out | Device to use for standard output; NULL = no redirection. |
std err | Device to use for standard error; NULL = no redirection. |
envs [] | List of environment symbols to define, or NULL. |
{ #if (defined (__UNIX__)) /************************************************************************* ** UNIX *************************************************************** *************************************************************************/ pid_t fork_result; /* Result from fork() */ int pipe_handle [2], /* Parent-to-child pipe */ pipe_readsize, /* Amount of data read from pipe */ pipe_data; /* Data read from pipe */ struct itimerval timeout; /* Wait for response from child */ struct sigaction old_handler; /* Old handler for SIGALRM */ const char *filename_only; /* Filename, without arguments */ char *clean_filename, /* Unescaped filename */ *full_filename; /* File to execute, with path */ /* Create pipe for feedback from child to parent; quit if this fails */ if (pipe (pipe_handle)) return (0); /* Create subprocess - this returns 0 if we are the child, the pid if */ /* we are the parent, or -1 if there was an error (not enough memory). */ fork_result = fork (); if (fork_result < 0) /* < 0 is an error */ { close (pipe_handle [0]); /* Close the pipe */ close (pipe_handle [1]); return (0); /* Could not fork */ } else if (fork_result > 0) /* > 0 is the parent process */ { /* --- PARENT PROCESS HANDLING ------------------------------------ */ /* If the child process has a problem with the exec() call, it */ /* sends us an errno value across the pipe. If the exec() call */ /* works okay, we get no feedback across the pipe. We wait for a */ /* small time (number of msecs specified by process_delay). If */ /* nothing comes across the pipe, we assume everything went okay. */ /* The FD_CLOEXEC setting *should* cause the child pipe to close */ /* after exec() but this does not seem to work; the read() still */ /* blocks. Bummer. */ if (process_delay > 0) { timeout.it_interval.tv_sec = 0; timeout.it_interval.tv_usec = 0; timeout.it_value.tv_sec = process_delay / 1000; timeout.it_value.tv_usec = (process_delay % 1000) * 1000; /* Save old signal handler to be polite to the calling program */ /* then redirect the SIGALRM signal to our own (empty) handler */ sigaction (SIGALRM, NULL, &old_handler); signal (SIGALRM, ignore_signal); setitimer (ITIMER_REAL, &timeout, 0); /* Now read on the pipe until data arrives or the alarm goes */ pipe_readsize = read (pipe_handle [0], &pipe_data, sizeof (errno)); /* Restore old signal handler */ sigaction (SIGALRM, &old_handler, NULL); } else pipe_readsize = 0; close (pipe_handle [0]); /* Close the pipe */ close (pipe_handle [1]); if (pipe_readsize == -1) { if (errno == EBADF || errno == EINTR) { /* Normal - SIGALRM arrived or FD_CLOEXEC worked :) */ if (wait) waitpid (fork_result, 0, 0); return ((PROCESS) fork_result); } else return (0); /* Error on read() */ } else /* We come here if process_delay was zero, or FD_CLOEXEC did its */ /* job and the pipe was closed by the child process. */ if (pipe_readsize == 0) { if (wait) waitpid (fork_result, 0, 0); return ((PROCESS) fork_result); } else { /* We read data from the pipe - this is an error feedback from */ /* the child - i.e. file not found, or a permission problem. */ errno = pipe_data; /* Stuff the errno */ return (0); } } /* --- CHILD PROCESS HANDLING ----------------------------------------- */ /* Prepare the process environment and execute the file */ /* If argv[] array was not supplied, build it now from filename */ /* And pull out the name of the file that we want to run. */ if (!argv) { /* Split off the arguments, and pick out the filename to use */ argv = tok split (filename); /* The filename, and only the filename, is the 0th argument */ filename_only = argv[0]; } else { /* Already got our arguments, so the filename is just the filename */ filename_only = filename; } /* If requested, close stdin, stdout, stderr, and redirect them */ redirect_io (std_in, STDIN_FILENO, pipe_handle [1], TRUE); redirect_io (std_out, STDOUT_FILENO, pipe_handle [1], FALSE); redirect_io (std_err, STDERR_FILENO, pipe_handle [1], FALSE); /* Find file on path, make sure it is executable */ /* This is a good moment to unescape any spaces in the filename... */ clean_filename = process unesc (NULL, filename_only); if (strchr (clean_filename, '/') == NULL && strchr (clean_filename, PATHEND) == NULL) full_filename = file where ('r', "PATH", clean_filename, NULL); else full_filename = file where ('r', NULL, clean_filename, NULL); mem_free (clean_filename); if (full_filename == NULL) { errno = ENOENT; /* No such file */ write (pipe_handle [1], &errno, sizeof (errno)); exit (EXIT_FAILURE); /* Kill the child process */ } if (!file is executable (full_filename)) { errno = EACCES; /* No permission to access file */ write (pipe_handle [1], &errno, sizeof (errno)); exit (EXIT_FAILURE); /* Kill the child process */ } /* Tell the system to close the pipe when we've done the exec() */ fcntl (pipe_handle [0], F_SETFD, FD_CLOEXEC); fcntl (pipe_handle [1], F_SETFD, FD_CLOEXEC); /* If requested, change to working directory */ if (workdir) chdir (workdir); /* Execute the program - normally this call does not return, as it */ /* replaces the current process image by the new one. If we ever do */ /* return, it is because there was an error. */ if (envv) /* If caller provided envv, use it */ execve (full_filename, argv, envv); else /* Otherwise use current values */ execv (full_filename, argv); write (pipe_handle [1], &errno, sizeof (errno)); exit (EXIT_FAILURE); /* Kill the child process */ #elif (defined (__OS2__)) /************************************************************************* ** OS/2 *************************************************************** *************************************************************************/ int process = 0; /* Process number */ HANDLE old_stdin = -1, /* Dup'd handle for old stdin */ old_stdout = -1, /* Dup'd handle for old stdout */ old_stderr = -1; /* Dup'd handle for old stderr */ int parsedargs = 0, /* argv() points at token array */ free_argv = 0; /* argv() points at handbuilt array */ const char *filename_only = NULL, /* Filename, without arguments */ *actual_command = NULL; /* Actual command string to run */ char *clean_filename = NULL, /* Unescaped filename */ *full_filename = NULL, /* File to execute, with path */ *curdir = NULL, /* Current working directory */ *strfree_this = NULL; /* strfree() this, if not NULL */ /* NOTE: special care must be taken to ensure this code does not leak */ /* memory, as the memory will be leaked in the main process which */ /* potientally tries to run for a long period of time. Token arrays */ /* have a lot of potiental for leaks if care is not taken. To avoid */ /* these potiental problems strings are copied a little more than */ /* otherwise would have been done, and then the original token arrays */ /* are freed. */ /* If argv[] array was not supplied, build it now from filename */ /* And pull out the name of the file that we want to run. */ if (!argv) { /* Split off the arguments, and pick out the filename to use */ argv = tok split (filename); /* The filename, and only the filename, is the 0th argument */ filename_only = argv[0]; parsedargs = 1; /* Yes, we split off the arguments */ } else { /* Already got our arguments, so the filename is just the filename */ filename_only = filename; } /* Under OS/2, we accept the magic file headers "#!", and "'/'*!". */ /* We also special case running CMD scripts, so that we invoke the */ /* default command interpreter, with a "/c" parameter, and the script */ /* name. The magic file headers are checked first so can be used to */ /* override the default command interpreter. */ actual_command = redirect_exec (filename_only); if (actual_command != NULL) { /* At this point we have a string containing the name of the */ /* program to run, followed by the arguments and the scriptname, */ /* if it was a script that we were going to run. So we tokenise the*/ /* string we got back and arrange for those bits to end up in the */ /* arguments if required. */ char **newargs = NULL; int num_new = 0, num_existing = 0; int free_newargs = 0; newargs = tok split (actual_command); /* Split off the arguments */ actual_command = newargs[0]; /* Count the number of new arguments (should be at least 1) */ /* And while we are here, eliminate any double quotes around the */ /* arguments (especially the script name), since they'll only get */ /* in the way later. */ for (num_new = 0; newargs[num_new] != NULL; num_new++) if (*newargs[num_new] == '"') { char *pair = NULL; pair = strrchr(newargs[num_new], '"'); if (pair != NULL) { *pair = '\0'; /* Eliminate the last " */ newargs[num_new]++; /* Step over the first one */ } } ASSERT(num_new >= 1); /* Count the number of existing arguments (from above), should be */ /* at least 1. */ for (num_existing = 0; argv[num_existing] != NULL; num_existing++) ; /* EMPTY BODY */ ASSERT(num_existing >= 1); /* Handle .CMD script files where the redirection wasn't done above */ if (num_new == 1) { /* Okay, it didn't expand there. But possibly we have a CMD */ /* script and need to invoke the command processor. */ char *extension = NULL; /* Find file extension; if not found, set to NULL */ extension = strrchr (actual_command, '.'); if (extension == NULL || strchr (extension, '/') /* Last '.' is part of path */ || strchr (extension, '\\')) /* => filename has no ext */ extension = NULL; if (extension != NULL && (lexcmp(extension, ".CMD") == 0)) { /* This is a CMD script, and we need to invoke the command */ /* interpreter over it. */ char *command_processor = NULL; command_processor = strdupl (env get string ("COMSPEC", "")); if (*command_processor != '\0') /* Not an empty string */ { /* Determine command processor arguments */ char **cmdargs = NULL; char **tmpargs = NULL; cmdargs = tok split (command_processor); /* Count the number of new arguments (at least 1) */ for (num_new = 0; cmdargs[num_new] != NULL; num_new++) ; /* EMPTY BODY */ ASSERT(num_new >= 1); /* Now merge those arguments with script name */ /* Need: num_new + 1 for "/c", +1 for script name */ /* + 1 to terminate array */ tmpargs = mem_alloc((num_new+3) * sizeof(char *)); if (tmpargs != NULL) { /* Okay, copy all the arguments into place */ int i = 0; for (i = 0; i < num_new; i++) tmpargs[i] = strdupl (cmdargs[i]); tmpargs[num_new++] = strdupl ("/c"); tmpargs[num_new++] = strdupl (actual_command); tmpargs[num_new] = NULL; /* Free the old arguments, and the old parse */ tok free(newargs); tok free(cmdargs); /* Now use that for our new arguments */ newargs = tmpargs; actual_command = newargs[0]; free_newargs = 1; /* Must free newargs later */ } /* Free the command processor string */ strfree(&command_processor); } } /* extension is .cmd */ } /* only one new argument (filename to run) */ /* Now collect all the arguments together into one array */ if (num_new >= 2 && num_existing >= 2) { /* Okay, we've got arguments to merge together, so we put the */ /* new ones first followed by the old ones. */ char **tmpargs; ASSERT(newargs != NULL); /* Allocate space for the new arguments (at start), and the */ /* existing arumgnents (at end), and a terminator. */ tmpargs = mem_alloc((num_new+num_existing+1) * sizeof(char *)); if (tmpargs != NULL) { /* Okay, copy all the arguments into place */ int i = 0; for (i = 0; i < num_new; i++) tmpargs[i] = strdupl (newargs[i]); /* NOTE: We skip the first argument here, since it is the */ /* name of the script, and we've got one of those above. */ /* BUT we've got to put next arg in next position, hence -1*/ for (i = 1; i < num_existing; i++) tmpargs[num_new + i - 1] = strdupl (argv[i]); /* Terminate the array of arguments */ tmpargs[num_new + num_existing - 1] = NULL; /* Pick up a new pointer to the command to run */ actual_command = tmpargs[0]; /* Tidy up after ourselves */ if (free_newargs) { int j = 0; for (j = 0; newargs[j] != NULL; j++) strfree(&newargs[j]); mem_free(newargs); } else tok free (newargs); if (parsedargs) { tok free(argv); parsedargs = 0; } /* Change pointer to point at the new (combined) arguments */ argv = tmpargs; free_argv = 1; } else { /* We couldn't allocate the new memory required */ /* Return failure. */ tok free(newargs); if (parsedargs) tok free(argv); errno = ENOMEM; return ((PROCESS)0); } } else if (num_new >= 2 && num_existing <= 1) { /* There were no arguments before, there are now. Use new ones */ if (parsedargs) { /* We parsed the arguments, free up some of the memory */ tok free(argv); parsedargs = 0; } argv = newargs; if (free_newargs) /* Make sure we free arguments */ free_argv = 1; else parsedargs = 1; } else /* (num_new <= 1) */ /* num_existing is 1 or more */ { /* No expansion of the string, we just use the existing args */ /* But we do use the string as returned, because it may have */ /* an extension on it. */ ASSERT(num_new <= 1); ASSERT(num_existing >= 1); /* Copy the string as returned, so that we can use it below */ strfree_this = strdupl (actual_command); if (strfree_this != NULL) { actual_command = strfree_this; ASSERT(free_newargs == 0); tok free(newargs); } } } /* Redirection found a filename to run */ else { /* Redirection failed. This means that it isn't executable, because*/ /* we should either have got a full name back, or a command string */ /* to run. */ if (parsedargs) tok free(argv); errno = EACCES; /* No permission to access file */ return (PROCESS)0; } /* Find file on path, make sure it is executable */ /* This is a good moment to unescape any spaces in the filename... */ clean_filename = process unesc (NULL, actual_command); if (strchr (clean_filename, '/') == NULL && strchr (clean_filename, PATHEND) == NULL) full_filename = file where ('r', "PATH", clean_filename, NULL); else full_filename = file where ('r', NULL, clean_filename, NULL); mem_free (clean_filename); if (full_filename == NULL) { /* Clear out the memory that we don't need any longer */ if (parsedargs) tok free(argv); else if (free_argv) { int j = 0; for (j = 0; argv[j] != NULL; j++) strfree(&argv[j]); mem_free(argv); } if (strfree_this != NULL) strfree(&strfree_this); errno = ENOENT; /* No such file */ return (PROCESS)0; /* Failed to open */ } if (!file is executable (full_filename)) { /* Clear out the memory that we don't need any longer */ if (parsedargs) tok free(argv); else if (free_argv) { int j = 0; for (j = 0; argv[j] != NULL; j++) strfree(&argv[j]); mem_free(argv); } if (strfree_this != NULL) strfree(&strfree_this); errno = EACCES; /* No permission to access file */ return (PROCESS)0; } /* Redirect the IO streams, and save copies of the ones we redirect */ old_stdin = redirect_io(std_in, STDIN_FILENO, 0, TRUE); old_stdout = redirect_io(std_out, STDOUT_FILENO, 0, FALSE); old_stderr = redirect_io(std_err, STDERR_FILENO, 0, FALSE); if (old_stdin == -2 || old_stdout == -2 || old_stderr == -2) { /* An error redirecting one of the file handles; restore them all */ /* and exit having failed our job. */ restore_redirection(old_stdin, old_stdout, old_stderr); /* Clear out the memory that we don't need any longer */ if (parsedargs) tok free(argv); else if (free_argv) { int j = 0; for (j = 0; argv[j] != NULL; j++) strfree(&argv[j]); mem_free(argv); } if (strfree_this != NULL) strfree(&strfree_this); return (PROCESS)0; } /* If requested, change to working directory */ if (workdir) { curdir = getcwd(NULL, 256); chdir (workdir); } else curdir = NULL; /* Spawn the new program, and pick up its process ID. */ if (envv) /* If caller provided envv, use it */ process = spawnve (P_NOWAIT, full_filename, argv, envv); else /* Otherwise use the current values */ process = spawnv (P_NOWAIT, full_filename, argv); /* Put things back the way they were before */ restore_redirection(old_stdin, old_stdout, old_stderr); if (curdir != NULL) /* If directory changed, restore it */ { chdir(curdir); free(curdir); } /* Clear out the memory that we don't need any longer */ if (parsedargs) tok free(argv); else if (free_argv) { int j = 0; for (j = 0; argv[j] != NULL; j++) strfree(&argv[j]); mem_free(argv); } if (strfree_this != NULL) strfree(&strfree_this); if (process <= -1) return ((PROCESS)0); /* Error starting child process */ if (wait) waitpid (process, 0, 0); return ((PROCESS) process); #elif (defined (WIN32)) /************************************************************************* ** WINDOWS 32 ********************************************************* *************************************************************************/ PROCESS process; /* Our created process handle */ STARTUPINFO newinfo = {0}, /* Specification for new process */ curinfo; /* Specification of cur process */ PROCESS_INFORMATION procinfo; /* Information about created proc */ DWORD dwCreateFlags = CREATE_NEW_CONSOLE; char *olddir, /* Caller's working directory */ *fulldir, /* Process' working directory */ *args, /* Command arguments, if any */ *actual_command, /* Command, possibly qualified */ *buffer = NULL; /* Working buffer */ int argn; /* Argument number */ /* Format full working directory, if specified */ if (workdir) { olddir = get curdir (); /* Just a lazy way to let the OS */ set curdir (workdir); /* figure-out if the workdir is a */ fulldir = get curdir (); /* relative or absolute directory. */ set curdir (olddir); mem_free (olddir); } else fulldir = NULL; /* Under Windows we accept the magic file header "#!". If the */ /* caller supplied an argument list, we attach this to the command. */ actual_command = redirect_exec (filename); strconvch (actual_command, '/', '\\'); GetShortPathName (actual_command, actual_command, strlen (actual_command) + 1); args = strchr (filename, ' '); /* Find arguments, if any */ if (argv) { /* Build full command buffer */ buffer = mem_alloc (tok text size ((char **) argv) + strlen (actual_command) + 1); strcpy (buffer, actual_command); for (argn = 1; argv [argn]; argn++) xstrcat (buffer, " ", argv [argn], NULL); actual_command = buffer; } else if (args) { buffer = xstrcpy (NULL, actual_command, args, NULL); actual_command = buffer; } process = mem_alloc (sizeof (PROC_HANDLE)); process-> process = NULL; process-> in = redirect_io (std_in, 0, 0, TRUE); process-> out = redirect_io (std_out, 0, 0, FALSE); process-> err = redirect_io (std_err, 0, 0, FALSE); /* Convert environment to a Windows-type packed block of strings */ /* Use supplied environment, or parent environment if necessary. */ process-> envd = strt2descr (envv? (char **) envv: environ); GetStartupInfo (&curinfo); newinfo.cb = sizeof (newinfo); newinfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; newinfo.wShowWindow = SW_HIDE; newinfo.hStdInput = process-> in? process-> in: curinfo.hStdInput; newinfo.hStdOutput = process-> out? process-> out: curinfo.hStdOutput; newinfo.hStdError = process-> err? process-> err: curinfo.hStdError; newinfo.lpTitle = NULL; /* If necessary, run in separate VM, for 16-bit programs */ if (process_compatible) dwCreateFlags |= CREATE_SEPARATE_WOW_VDM; /* CreateProcess returns errors sometimes, even when the process was */ /* started correctly. The cause is not evident. For now: we detect */ /* an error by checking the value of procinfo.hProcess after the call. */ procinfo.hProcess = NULL; CreateProcess ( NULL, /* Name of executable module */ actual_command, /* Command line string */ NULL, /* Process security attributes */ NULL, /* Thread security attributes */ TRUE, /* Handle inheritance flag */ dwCreateFlags, /* Creation flags */ process-> envd-> data, /* New environment block */ fulldir, /* Current directory name */ &newinfo, /* STARTUPINFO */ &procinfo); /* PROCESS_INFORMATION */ mem_strfree (&fulldir); mem_strfree (&buffer); /* Deallocate buffer, if used */ if (procinfo.hProcess == NULL) /* Error, we presume */ { process close (process); return (NULL); } /* Release our hold on the thread */ CloseHandle (procinfo.hThread); process-> process = procinfo.hProcess; /* We do not need access to the files any longer in this process */ if (process-> in) { CloseHandle (process-> in); process-> in = NULL; } if (process-> out) { CloseHandle (process-> out); process-> out = NULL; } if (process-> err) { CloseHandle (process-> err); process-> err = NULL; } /* Wait for the process to finish or be cancelled */ if (wait) { WaitForSingleObject (procinfo.hProcess, INFINITE); process close (process); } return (process); #elif (defined (__VMS__)) /************************************************************************* ** OPENVMS ************************************************************ *************************************************************************/ PROCESS process; /* Our created process handle */ char *curdir, /* Current directory */ *clean_filename, /* Unescaped filename */ *full_filename = NULL, *full_std_in = NULL, *full_std_out = NULL; qbyte process_flags; /* Process creation flags */ int argn, /* Argument number */ rc; /* Return code from lib$spawn */ Bool rebuilt_argv = FALSE; /* Did we rebuild argv[]? */ VMS_STRING (command_dsc, ""); /* Define string descriptors */ VMS_STRING (std_in_dsc, ""); VMS_STRING (std_out_dsc, ""); /* If argv[] array was not supplied, build it now from filename */ if (!argv) { argv = tok split (filename); filename = argv [0]; rebuilt_argv = TRUE; } /* If filename contains a path or extension, disregard them */ clean_filename = strrchr (filename, '/'); if (clean_filename) clean_filename++; else clean_filename = (char *) filename; if (strchr (clean_filename, '.')) *strchr (clean_filename, '.') = '\0'; /* Rebuild full command from filename and arguments */ full_filename = mem_alloc (tok text size ((char **) argv) + strlen (clean_filename) + 1); strcpy (full_filename, clean_filename); for (argn = 1; argv [argn]; argn++) xstrcat (full_filename, " ", argv [argn], NULL); /* Free argument table if we allocated it dynamically here */ if (rebuilt_argv) tok free (argv); command_dsc.value = full_filename; command_dsc.length = strlen (full_filename); /* Prepare full names for stdin and stdout */ curdir = get curdir (); if (std_in) { if (strchr (std_in, '/')) /* If already with path, use as is */ full_std_in = mem_strdup (std_in); else full_std_in = xstrcpy (NULL, curdir, "/", std_in, NULL); translate_to_vms (full_std_in); std_in_dsc.value = full_std_in; } if (std_out) { if (strchr (std_out, '/')) /* If already with path, use as is */ full_std_out = mem_strdup (std_out); else full_std_out = xstrcpy (NULL, curdir, "/", std_out, NULL); translate_to_vms (full_std_out); std_out_dsc.value = full_std_out; } std_in_dsc.length = std_in? strlen (std_in_dsc.value): 0; std_out_dsc.length = std_out? strlen (std_out_dsc.value): 0; /* If requested, change to working directory */ if (workdir) chdir (workdir); /* Prepare process flags */ if (wait) process_flags = 0; else process_flags = 1; /* Bit 1 = don't wait for child */ process = mem_alloc (sizeof (PROC_HANDLE)); process-> id = 0; process-> status = 0; /* Completion status */ /* char *envv [], */ /* Environment variables, or NULL */ rc = lib$spawn ( &command_dsc, /* Command to run */ std_in? &std_in_dsc: NULL, /* Stdin descriptor */ std_out? &std_out_dsc: NULL, /* Stdout+stderr */ &process_flags, /* Options for new process */ &NULL, /* Process name -- generated */ &process-> id, /* Returned process ID */ &process-> status); if (workdir) /* Switch back to original dir */ chdir (curdir); mem_free (curdir); mem_strfree (&full_filename); /* Deallocate various buffers, */ mem_strfree (&full_std_in); /* if they were used */ mem_strfree (&full_std_out); /* */ /* Return process ID. If we waited for completion, the process id */ /* is always NULL. */ if (rc != 1) /* Process failed with error */ { process close (process); process = NULL; } else if (wait) /* Finished with process */ process close (process); return (process); #else return ((PROCESS) 0); /* Not supported on this system */ #endif }
| << | < | > | >> |
![]() |