Problem/Motivation
On one of our websites, $settings['file_private_path']
points to a folder on an NFS mount.
Trying to download an image fails with this exception:
Got error 'PHP message: Uncaught PHP Exception Symfony\\Component\\HttpFoundation\\File\\Exception\\FileException: "File must be readable." at /home/insite/staging/sites/insite-losange-master/vendor/symfony/http-foundation/BinaryFileResponse.php line 97'
This happens because BinaryFileResponse::setFile()
calls File::isReadable()
on the private://
URI, then PHP calls PrivateStream::url_stat()
and tries to decide if it can read the file.
This does not work because PrivateStream::url_stat()
returns an UID and GID specific to NFS, that does not exist on the host system.
We can also reproduce this by comparing these calls that both point to the same file:
// Returns FALSE.
is_readable('private://foo.jpg');
// Returns TRUE.
is_readable('/mnt/nfs/foo.jpg');
Internally, when is_readable()
or is_writable()
is called on a path, PHP uses the access()
system call to check access. This works for NFS because the system knows how to translate NFS permissions.
But when called on a stream wrapper, PHP uses the information returned by url_stat()
to know if it has access to the file.
This does not work if the NFS user is different from the PHP user.
Steps to reproduce
Proposed resolution
As a workaround, we extended PrivateStream
like this:
<?php
namespace Drupal\foo\StreamWrapper;
use Drupal\Core\StreamWrapper\PrivateStream;
class NfsPrivateStream extends PrivateStream {
/**
* @param $uri
* @param $flags
*
* @return array|false
* @link https://github.com/aws/aws-sdk-php/blob/93d6049a3a412818cf3df379b662d2e0b75252d0/src/S3/StreamWrapper.php#L775
*/
public function url_stat($uri, $flags) {
$stat = parent::url_stat($uri, $flags);
$realpath = $this->getLocalPath($uri);
if (is_writable($realpath)) {
if (is_file($realpath)) {
$stat['mode'] = $stat[2] = 0100777;
}
elseif (is_dir($realpath)) {
$stat['mode'] = $stat[2] = 0040777;
}
}
return $stat;
}
}
We call is_writable()
on the full path (so the real access is checked by the system) and if true we lie to PHP by saying the file is fully writable and readable (777 mode).
This works for our specific use case, because we know that if PHP can write to the NFS path, it will be able to also read it.
A more general solution would probably be more complex.