diff --git a/src/filesystem/Filesystem.php b/src/filesystem/Filesystem.php --- a/src/filesystem/Filesystem.php +++ b/src/filesystem/Filesystem.php @@ -1118,11 +1118,43 @@ * @task assert */ public static function assertExists($path) { - if (!self::pathExists($path)) { - throw new FilesystemException( - $path, - pht("File system entity '%s' does not exist.", $path)); + if (self::pathExists($path)) { + return; + } + + // Before we claim that the path doesn't exist, try to find a parent we + // don't have "+x" on. If we find one, tailor the error message so we don't + // say "does not exist" in cases where the path does exist, we just don't + // have permission to test its existence. + foreach (self::walkToRoot($path) as $parent) { + if (!self::pathExists($parent)) { + continue; + } + + if (!is_dir($parent)) { + continue; + } + + // Note that we don't need read permission ("+r") on parent directories + // to determine that a path exists, only execute ("+x"). + + if (!is_executable($parent)) { + throw new FilesystemException( + $path, + pht( + 'Filesystem path "%s" can not be accessed because a parent '. + 'directory ("%s") is not executable (the current process does '. + 'not have "+x" permission).', + $path, + $parent)); + } } + + throw new FilesystemException( + $path, + pht( + 'Filesystem path "%s" does not exist.', + $path)); } diff --git a/src/future/exec/PhutilExecutableFuture.php b/src/future/exec/PhutilExecutableFuture.php --- a/src/future/exec/PhutilExecutableFuture.php +++ b/src/future/exec/PhutilExecutableFuture.php @@ -126,13 +126,47 @@ * @task config */ final public function setCWD($cwd) { - $cwd = (string)$cwd; + $cwd = phutil_string_cast($cwd); + + try { + Filesystem::assertExists($cwd); + } catch (FilesystemException $ex) { + throw new PhutilProxyException( + pht( + 'Unable to run a command in directory "%s".', + $cwd), + $ex); + } if (!is_dir($cwd)) { throw new Exception( pht( - 'Preparing to run a command in directory "%s", but that '. - 'directory does not exist.', + 'Preparing to run a command in directory "%s", but that path is '. + 'not a directory.', + $cwd)); + } + + // Although you don't technically need read permission to "chdir()" into + // a directory, it is almost certainly a mistake to execute a subprocess + // in a CWD we can't read. Refuse to do this. If callers have some + // exceptionally clever scheme up their sleeves they can always have the + // subprocess "cd" or "chdir()" explicitly. + + if (!is_readable($cwd)) { + throw new Exception( + pht( + 'Preparing to run a command in directory "%s", but that directory '. + 'is not readable (the current process does not have "+r" '. + 'permission).', + $cwd)); + } + + if (!is_executable($cwd)) { + throw new Exception( + pht( + 'Preparing to run a command in directory "%s", but that directory '. + 'is not executable (the current process does not have "+x" '. + 'permission).', $cwd)); }