diff --git a/src/filesystem/TempFile.php b/src/filesystem/TempFile.php --- a/src/filesystem/TempFile.php +++ b/src/filesystem/TempFile.php @@ -20,6 +20,7 @@ private $file; private $preserve; private $destroyed = false; + private $maskUnlinkFailure = false; /* -( Creating a Temporary File )------------------------------------------ */ @@ -64,6 +65,20 @@ return $this; } + /** + * Sets whether or not any errors during unlinking should be masked. This + * is useful if an external process may still have a lock on the file (under + * Windows). + * + * @param bool True to mask any errors occurring during unlinking. + * @return this + * @task config + */ + public function setMaskUnlinkFailure($mask) { + $this->maskUnlinkFailure = $mask; + return $this; + } + /* -( Internals )---------------------------------------------------------- */ @@ -95,13 +110,27 @@ return; } - Filesystem::remove($this->dir); + if ($this->maskUnlinkFailure) { + try { + @Filesystem::remove($this->dir); + } catch (FilesystemException $ex) { + } + } else { + Filesystem::remove($this->dir); + } // NOTE: tempnam() doesn't guarantee it will return a file inside the // directory you passed to the function, so we make sure to nuke the file // explicitly. - Filesystem::remove($this->file); + if ($this->maskUnlinkFailure) { + try { + @Filesystem::remove($this->file); + } catch (FilesystemException $ex) { + } + } else { + Filesystem::remove($this->file); + } $this->file = null; $this->dir = null; diff --git a/src/future/exec/ExecFuture.php b/src/future/exec/ExecFuture.php --- a/src/future/exec/ExecFuture.php +++ b/src/future/exec/ExecFuture.php @@ -564,6 +564,23 @@ } do { + $real_length = $length; + + // On Windows, we must check to see if we can read without blocking + // from the stream, and even then we must read only 1 byte at a time. + if (phutil_is_windows()) { + $r = array($stream); + $w = array(); + $e = array(); + if (false === stream_select($r, $w, $e, 0)) { + throw new Exception('stream_select() failed'); + } + $real_length = 0; + if (in_array($stream, $r)) { + $real_length = 1; + } + } + $data = fread($stream, min($length, 64 * 1024)); if (false === $data) { throw new Exception('Failed to read from '.$description); @@ -662,13 +679,38 @@ } else { $trap = null; } + + $stdout_target = null; + $stderr_target = null; + + $spec = self::$descriptorSpec; + if (phutil_is_windows()) { + $stdout_target = id(new TempFile())->setMaskUnlinkFailure(true); + $stderr_target = id(new TempFile())->setMaskUnlinkFailure(true); + + $spec = array( + 0 => self::$descriptorSpec[0], // stdin + 1 => fopen($stdout_target, 'wb'), // stdout + 2 => fopen($stderr_target, 'wb'), // stderr + ); + } $proc = @proc_open( $unmasked_command, - self::$descriptorSpec, + $spec, $pipes, $cwd, $env); + + if (phutil_is_windows()) { + fclose($spec[1]); + fclose($spec[2]); + $pipes = array( + 0 => head($pipes), // stdin + 1 => fopen($stdout_target, 'rb'), // stdout + 2 => fopen($stderr_target, 'rb'), // stderr + ); + } if ($trap) { $err = $trap->getErrorsAsString(); @@ -685,12 +727,12 @@ $this->proc = $proc; list($stdin, $stdout, $stderr) = $pipes; - + if (!phutil_is_windows()) { - // On Windows, there's no such thing as nonblocking interprocess I/O. - // Just leave the sockets blocking and hope for the best. Some features - // will not work. + // On Windows, we redirect process standard output and standard error + // through temporary files, and then use stream_select to determine + // if there's more data to read. if ((!stream_set_blocking($stdout, false)) || (!stream_set_blocking($stderr, false)) || @@ -732,14 +774,14 @@ $status = $this->procGetStatus(); $read_buffer_size = $this->readBufferSize; - + $max_stdout_read_bytes = PHP_INT_MAX; $max_stderr_read_bytes = PHP_INT_MAX; if ($read_buffer_size !== null) { $max_stdout_read_bytes = $read_buffer_size - strlen($this->stdout); $max_stderr_read_bytes = $read_buffer_size - strlen($this->stderr); } - + if ($max_stdout_read_bytes > 0) { $this->stdout .= $this->readAndDiscard( $stdout, @@ -755,8 +797,45 @@ 'stderr', $max_stderr_read_bytes); } - + if (!$status['running']) { + if (phutil_is_windows()) { + fclose($stdout); + fclose($stderr); + } + + /*if (phutil_is_windows()) { + // On Windows, because we're reading using stream_select, we need to + // read all remaining data when the process exits (because we're only + // reading 1 byte at a time from each stream). This ensures we get all + // remaining data from the process. + $max_stdout_read_bytes = PHP_INT_MAX; + $max_stderr_read_bytes = PHP_INT_MAX; + if ($read_buffer_size !== null) { + $max_stdout_read_bytes = $read_buffer_size - strlen($this->stdout); + $max_stderr_read_bytes = $read_buffer_size - strlen($this->stderr); + } + + if ($max_stdout_read_bytes > 0) { + $this->stdout .= $this->readAndDiscard( + $stdout, + $this->getStdoutSizeLimit() - strlen($this->stdout), + 'stdout', + $max_stdout_read_bytes); + } + + if ($max_stderr_read_bytes > 0) { + $this->stderr .= $this->readAndDiscard( + $stderr, + $this->getStderrSizeLimit() - strlen($this->stderr), + 'stderr', + $max_stderr_read_bytes); + } + + fclose($stdout); + fclose($stderr); + }*/ + $this->result = array( $status['exitcode'], $this->stdout,