Problem/Motivation
When a stream wrapper path (e.g. private://some-path, temporary://some-path, etc) is passed as the $directory parameter to
FileSystem::tempnam() (or drupal_tempnam() in D7), the unique filename returned is always in the root path of the file system represented by the stream wrapper without any of the subdirectory paths included in the $directory parameter.
Steps to reproduce
Create a filename for stream wrapper path using FileSystem::tempnam():
// Create a filename in '/tmp/some-path'.
$filename = \Drupal::service('file_system')->tempnam('temporary://some-path', 'prefix_');
Expected $filename value: /tmp/some-path/prefix_random-filename
Actual $filename value: /tmp/prefix_random-filename
Proposed resolution
Modify FileSystem::tempnam() to return a filename that includes any subdirectories passed as part of $directory parameter containing stream wrapper paths.
Remaining tasks
Review submited patches.
User interface changes
None.
API changes
Currently if stream wrapper path including a non-existant / invalid subdirectory (e.g. private://non-existant-path) is passed as the $directory parameter to FileSystem::tempnam() a valid filename in the root path for the stream wrapper will be returned. After the proposed changes, FileSystem::tempname() will return FALSE when a stream wrapper path containing invalid subdirectories is used.
Data model changes
None.
Original report by Steven Jones
I was trying to do this:
// Create a file in '/tmp/some-path'.
$file = drupal_tempnam('temporary://some-path', 'prefix');
But the returned filename will always reside in the base directory of the 'temporary' wrapper, because the implementation of drupal_tempnam uses the wrappers getDirectoryPath method, which basically returns the root of the filesystem represented by the wrapper.
Is this on purpose? Or a bug that needs fixing?
If the former, the docs need changing.
| Comment | File | Size | Author |
|---|
Issue fork drupal-985384
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #1
steven jones commentedSo it gets a little messy if you start trying to work out the actual directory created by drupal_realpath() for temp directories, because if the directory doesn't exist, then you just end up with strange results.
But, this function otherwise (with an actual path) has different behaviour, and is not a drop in replacement for tempnam.
Maybe something like:
Would 'fix' the issue?
Comment #2
steven jones commentedPatch attached, that should address the issue I think, of course there are no tests in core for this. So these will need writing.
Comment #3
steven jones commentedAbove patch, re-rolled for HEAD, and with tests.
Comment #4
damien tournoud commentedGood catch, but this feels a little messy:
I think we just want:
Comment #5
damien tournoud commentedComment #6
steven jones commentedSomething like the attached then.
Comment #7
steven jones commentedScrap that last patch, needed a
file_stream_wrapper_uri_normalize()also. Added a test for that case too.Comment #8
steven jones commentedScrewed that patch up.
Comment #9
dawehnerSounds like a bug we have in D8 as well, see #2304461: KernelTestBaseTNG™
Comment #10
chalk commentedBug is still reproduced in latest DEV version of Drupal 7.
Patch is a little bit incorrect (2 'returns' after applying), but it works.
Comment #11
mfernea commentedIt works for us. I rerolled the patch.
Comment #13
loopduplicate commentedHere's a reroll. I couldn't do an interdiff between this and #8 because
it appears that the patch file in #8 is corrupt(Edit: because I wasn't used to the patch format. I added interdiffs in #14)#11 doesn't use file_stream_wrapper_uri_normalize().
Comment #14
loopduplicate commentedHere's interdiffs for 8-13 and 11-13
Comment #15
loopduplicate commentedMarking as needs review.
Comment #16
loopduplicate commentedI left the PHP doc blocks alone and didn't rename the test class. The new patch is very simple and focuses only on the task at hand. If doc blocks need updating and/or the test class needs to be renamed, I think those should be separate issues. Anyway, that's a minor thing.
Comment #18
loopduplicate commentedBased on the test results from #13, it looks like the testbot had a malfunction. Re-marking as needs review and re-queueing test.
Comment #19
loopduplicate commentedArgh, I added an extra line on accident. Updating patch.
Comment #20
oadaeh commentedThe patch in #19 looks good, applies cleanly, and executes the desired behavior.
Comment #21
kristen polRTBC++ that the patch is working for us. Thanks.
Comment #22
stefan.r commentedDon't we have this issue in Drupal 8 as well? See
FileSystem::tempnam()Comment #24
mmrares commentedI've created a port of the patch for Drupal 8.
Comment #25
mfernea commentedUpdated the status for tests.
Comment #27
loopduplicate commentedLooks good. The D8 version is missing tests so I ported the D7 ones.
Comment #29
Anonymous (not verified) commentedTo properly fixFileSystem::tempnam(), the code needs to be able to run on operating systems that use SystemD with aPrivateTmp=true, like recent RHEL/CentOS v7+.Even if you delete the `path.temporary` value from `system.file` config, the default temporary directory resolved by `TemporaryStream` wrapper class points to the global/tmpdirectory, instead of the private, per service directory like:EDIT 1: From what I can tell, in the context of a web service running on a SystemD powered systems, the `sys_get_temp_dir()` function should work just fine, and the path should be transparently resolved to the private directory, but this doesn't seem to be the case and the `is_writable()` call fromFileSystem::tempnam()fails, because it tries to check the main OS temporary directory.EDIT 2: It looks liketempnam($local_directory, $prefix)is able to create a file in the proper location, e.g.:But the call tois_writable($local_directory)returns FALSE.EDIT 3: Nevermind, looks like the destination directory for tempnam() needs to exist before calling it, otherwise:
Comment #30
Anonymous (not verified) commentedHere's my attempt at this. I've updated the
FileSystem::tempnam()method to behave like the PHP function and fallback to using the temporary directory, although I'm not sure if I should use thetemporary://prefix, since the fallback directory can be different from the directory resolved fromtemporary://.I've also added an extra test for this case.
Comment #31
Anonymous (not verified) commentedComment #32
miroslavbanov commentedI guess this behavior is documented in php.net, but not documented in Drupal. But the behavior is so confusing, it might be a good idea to document it here. It is after all a reasonable assumption, to expect that the path is not changed.
Comment #33
Anonymous (not verified) commentedPlease let me know what you think of the documentation changes.
Could you please comment on this line? Think that it might be wrong and I'm not exactly sure how this should be handled. Should we diverge from PHP's tempnam() function behavior or how should we handle it's fallback case? The system temp directory used as fallback must not be equivalent to this URI here.
How can we convert it to a proper scheme? Should we just fail if it isn't in the same scheme?
Comment #35
miroslavbanov commentedOn doc change, it is better than before, but it might use @link definition. Also I think it could make clear that this function can return 3 different things, and that's the behavior of php tempnam(). I mean - it is written already, but it could be written in a way that's very hard to miss.
---
Is it correct to keep the php tempnam() behavior, or can we have something better? I think it doesn't need to keep to php tempnam(), but it's already in use, so now is a different matter.
---
We can't be sure there even is a proper scheme. This may work:
Other things we can do is 1. return FALSE or 2. return $temp_file_path despite it not containing a scheme.
Comment #36
Anonymous (not verified) commentedAbout that
@linkdefinition, I'm not sure what you want to link to and from where? Maybe you want me to link to the `tempnam()` docs page instead of just@see tempnam()?I think the method should only return the fallback temporary file only when the requested directory is on the temporary stream wrapper. Otherwise, you return a different stream wrapper and this can get you into problems.
I've also normalized the returned stream wrapper and added a test for the temporary stream wrapper to deal with the differences.
But I think that we should have a consistent behavior in a different method and just deprecate
FileSystem::tempnam().Comment #37
Anonymous (not verified) commentedFixed test.
Comment #39
miroslavbanov commentedI think it is pretty good already. At the very least, much more sensible then what was before. I will try to find time to review and test this - maybe this weekend.
Edit1:
The last failing test may produce different results on different environments. I mean this:
$this->assertNotEquals($temporary_scheme . '://', $file_system->dirname($tempnam), 'Temporary file was created in the root of the temporary stream wrapper.');in fact the two strings will be equal if
sys_get_temp_dir() === $file_system->realpath('temporary://')Edit2:
Actually from looking at the test, maybe you meant to do assertEquals.
Comment #40
Anonymous (not verified) commentedYes, that should be it. Thanks.
Sorry for the test flip-flops, I currently don't have a setup to run them.
Comment #41
Anonymous (not verified) commentedDoes this count as breaking change or not? Should it go into D9 or D8.6/8.5?
After this patch, there is a case when the method doesn't create a temporary file where it used to.
Maybe this change"should be added back as a "deprecated" feature that is behind a flag (controllable via an extra parameter).
Comment #42
miroslavbanov commentedAbout #41
That temporary file's path wasn't returned correctly so it was never useful as far as I know. But this will surely be reviewed by others, so we'll know if the method can't be changed. If we end up deprecating things, the whole method should be deprecated, and a better new one should be created in my opinion.
Comment #43
ludo.rThis patch is a re-roll from #11 against Drupal core 7.65
Comment #45
vinmassaro commentedCan someone explain how the D7 patch is supposed to work?
It seems that the patch in #43 still doesn't return subdirectories because they are not being added in any way, and
drupal_basename()is going to always only return a filename. I came here from #2402663: Excel and CSV export randomly empty since webform usesdrupal_tempnam()and isn't returning the expected path when sending subdirectories todrupal_tempnam(). Here is a simple code sample I am using with devel that shows the subdirectory is not returned from drupal_tempnam() after applying this patch:Thanks.
Comment #46
steven jones commented@vinmassaro does that temporary directory exist on disk already?
drupal_tempnamwon't make new directories afaik, but should make files within those directories with the patch in #43.As for how does the patch work:
This is the crux of the patch.
$wrapper->getDirectoryPath()would always return the root path of the configured stream wrapper, but now we convert it to a local directory usingdrupal_realpaththis directory is then hopefully writeable, and since it's a local directory, the call totempnamshould work.Note that PHP reserves the right to generate and return a temporary file the system's temporary directory as per the docs on
tempnamhttps://php.net/manual/en/function.tempnam.php.Comment #47
steven jones commented@vinmassaro so sorry, you are totally right. The patch in #11 was an incorrect re-roll. The patch in #13 has the correct code, and probably needs a re-roll for Drupal 7.
@SchnWalter I don't think this is a 'breaking change' since callers ask for a URI to a temporary file, and we still return one, just one that's more in-line with what they want really. But happy to take a steer from the Drupal 8 maintainers.
Comment #48
liam morlandThis is a re-roll of #13.
Comment #49
liam morlandThis is a re-roll of #40.
Comment #52
liam morlandPatch #49 with deprecated file_stream_wrapper_uri_normalize() replaced.
Comment #54
liam morlandPatch #52 with deprecated FileSystem::validScheme() replaced.
Comment #55
liam morlandComment #57
liam morlandPatch #54 with deprecated file_default_scheme() replaced.
Comment #60
liam morlandRe-roll.
Comment #62
liam morlandDeprecations fixed.
Comment #64
liam morlandComment #65
liam morlandIs a version of this for 9.0.x required?
Comment #66
renguer0 commentedLast patch is working on 8.8.5, I really don't understand why it isn't added to core.
Thanks for all you people that are contributing to this, specially to @Liam Morland that keeps that alive from last year.
Comment #68
liam morlandRe-roll for 9.1.x.
Comment #69
liam morlandRe-roll.
Comment #70
joegraduateComment #76
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #80
kim.pepperCreated a MR from the patch in #69 and hid patch files.
Comment #81
kim.pepperLeft some feedback. NW for that and coding standard issue.
Comment #82
liam morlandAddressed feedback and coding standards.
Comment #83
kim.pepperConfirmed the test-only job correctly failed with:
NW for the test location.
Comment #84
liam morlandThe test cannot easily be moved because it uses
::createDirectory(), which is not available in any ofFileSystem*Test.Comment #88
mohit_aghera commentedLooking good to me.
MR is updated with latest main branch and is green.
I reviewed the changes and all the feedback seems addressed.
I reviewed the comment https://git.drupalcode.org/project/drupal/-/merge_requests/11681#note_48... related to moving class to `FileSystem*Test`
For testing, I tried to move test to `FileSystemTempDirectoryTest` and extended that class from `FileTestBase`
The test worked fine.
Although I am not quite sure whether we should move it to `FileSystemTempDirectoryTest` because based on name it seems like it is restricted to TempDirectoryTest.
Happy to update based on inputs from folks.
I'm keeping this in needs review for now.
Otherwise, all good from me.
Comment #89
kim.pepperDid another review and everything looks good. I'm happy not to move FileSystemTempDirectoryTest.
Comment #90
alexpottNice old issue - I think we need to use Drupal's FileSystemComponent::getOsTemporaryDirectory(); rather than sys_get_temp_dir() - especially if you look at \Drupal\Core\StreamWrapper\TemporaryStream
... or maybe we should be using $this->getTempDirectory()
Comment #91
alexpott