Skip to content

Commit

Permalink
Add File\leaky_temporary_file() (#181)
Browse files Browse the repository at this point in the history
Summary:
This is a temporary file that is not automatically deleted.

Name is following the "if it's sometimes necessary but you don't want
people to use it, name it something scary" principle :p

Adding this based on looking into why some open source Facebook projects
(e.g. fbshipit) use PHP IO instead of HSL IO.

`File\temporary_file()` should be strongly preferred over this, but
sometimes it's needed.

X-link: hhvm/hsl#181

Reviewed By: bigfootjon

Differential Revision: D34314199

fbshipit-source-id: 8513311c431861b2dfde9b8242702ef7f7585f5a
  • Loading branch information
fredemmott authored and facebook-github-bot committed Mar 15, 2022
1 parent 180f52c commit e5b344d
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 18 deletions.
30 changes: 30 additions & 0 deletions hphp/hsl/src/file/_Private/open_temporary_fd.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?hh
/*
* Copyright (c) 2004-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the hphp/hsl/ subdirectory of this source tree.
*
*/

namespace HH\Lib\_Private\_File;

use namespace HH\Lib\{OS, Str};

function open_temporary_fd(
string $prefix,
string $suffix,
): (OS\FileDescriptor, string) {
if (
!(
Str\starts_with($prefix, '/') ||
Str\starts_with($prefix, './') ||
Str\starts_with($prefix, '../')
)
) {
$prefix = Str\trim_right(\sys_get_temp_dir(), '/').'/'.$prefix;
}
$pattern = $prefix.'XXXXXX'.$suffix;
return OS\mkstemps($pattern, Str\length($suffix));
}
43 changes: 43 additions & 0 deletions hphp/hsl/src/file/leaky_temporary_file.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?hh
/*
* Copyright (c) 2004-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the hphp/hsl/ subdirectory of this source tree.
*
*/

namespace HH\Lib\File;

use namespace HH\Lib\File;
use namespace HH\Lib\_Private\_File;

/** Creates a new temporary file, without automatic cleanup.
*
* `File\temporary_file()` is **strongly** recommended instead.
*
* - If the prefix starts with `.`, it is interpreted relative to the current
* working directory.
* - If the prefix statis with `/`, it is treated as an absolute path.
* - Otherwise, it is created in the system temporary directory.
*
* Regardless of the kind of prefix, the parent directory must exist.
*
* A suffix can optionally be provided; this is useful when you need a
* particular filename extension; for example,
* `File\temporary_file('foo', '.txt')` may create `/tmp/foo123456.txt`.
*
* The temporary file:
* - will be a new file (i.e. `O_CREAT | O_EXCL`)
* - be owned by the current user
* - be created with mode 0600
* - **will not** be automatically deleted
*/
function leaky_temporary_file(
string $prefix = 'hack-leakytmp-',
string $suffix = '',
): File\CloseableReadWriteHandle {
list($fd, $path) = _File\open_temporary_fd($prefix, $suffix);
return new _File\CloseableReadWriteHandle($fd, $path);
}
18 changes: 4 additions & 14 deletions hphp/hsl/src/file/temporary_file.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@

namespace HH\Lib\File;

use namespace HH\Lib\{OS, Str};
use namespace HH\Lib\_Private\_File;

/** Create a new temporary file.
*
* The file is automatically deleted when the disposable is removed.
*
* - If the prefix starts with `.`, it is interpretered relative to the current
* - If the prefix starts with `.`, it is interpreted relative to the current
* working directory.
* - If the prefix statis with `/`, it is treated as an absolute path.
* - Otherwise, it is created in the system temporary directory.
Expand All @@ -32,22 +31,13 @@
* - will be a new file (i.e. `O_CREAT | O_EXCL`)
* - be owned by the current user
* - be created with mode 0600
* - will be automatically deleted when the object is disposed
*/
<<__ReturnDisposable>>
function temporary_file(
string $prefix = 'hack-tmp-',
string $suffix = '',
): TemporaryFile {
if (
!(
Str\starts_with($prefix, '/') ||
Str\starts_with($prefix, './') ||
Str\starts_with($prefix, '../')
)
) {
$prefix = Str\trim_right(\sys_get_temp_dir(), '/').'/'.$prefix;
}
$pattern = $prefix.'XXXXXX'.$suffix;
list($handle, $path) = OS\mkstemps($pattern, Str\length($suffix));
return new TemporaryFile(new _File\CloseableReadWriteHandle($handle, $path));
list($fd, $path) = _File\open_temporary_fd($prefix, $suffix);
return new TemporaryFile(new _File\CloseableReadWriteHandle($fd, $path));
}
26 changes: 22 additions & 4 deletions hphp/hsl/tests/file/FileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ final class FileTest extends HackTest {
'File should only be readable/writable by current user',
);
}
expect(file_exists($path))->toBeFalse();
expect(file_exists($path))->toBeFalse();

using ($tf = File\temporary_file('foo', '.bar')) {
$path = $tf->getHandle()->getPath();
Expand All @@ -64,13 +64,31 @@ final class FileTest extends HackTest {
$dir = sys_get_temp_dir().'/hsl-test-'.PseudoRandom\int(0, 99999999);
mkdir($dir);
using ($tf = File\temporary_file($dir.'/foo')) {
expect(
Str\starts_with($tf->getHandle()->getPath(), $dir.'/foo'),
)
expect(Str\starts_with($tf->getHandle()->getPath(), $dir.'/foo'))
->toBeTrue();
}
}

public async function testLeakyTemporaryFile(): Awaitable<void> {
$tf1 = File\leaky_temporary_file();
expect($tf1->getPath())->toNotContainSubstring('XXXXXX');

$tf2 = File\leaky_temporary_file();
expect($tf1->getPath())->toNotEqual($tf2->getPath());
expect(\file_exists($tf1->getPath()))->toBeTrue();
$tf1->close();
expect(\file_exists($tf1->getPath()))->toBeTrue();
await $tf2->writeAllAsync('hello');
$tf2->close();

// Make sure that the write completed, and no deletions or truncates
// happened
expect(\file_get_contents($tf2->getPath()))->toEqual('hello');

\unlink($tf1->getPath());
\unlink($tf2->getPath());
}

public async function testMultipleReads(): Awaitable<void> {
using ($tf = File\temporary_file()) {
$f = $tf->getHandle();
Expand Down

0 comments on commit e5b344d

Please sign in to comment.