Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
C
capnproto
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
submodule
capnproto
Commits
79f7d506
Commit
79f7d506
authored
Oct 24, 2016
by
Kenton Varda
Committed by
Kenton Varda
Apr 27, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement filesystem API -> POSIX abstraction.
parent
b99fcdd4
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
2151 additions
and
0 deletions
+2151
-0
filesystem-disk-test.c++
c++/src/kj/filesystem-disk-test.c++
+588
-0
filesystem-disk.c++
c++/src/kj/filesystem-disk.c++
+1563
-0
No files found.
c++/src/kj/filesystem-disk-test.c++
0 → 100644
View file @
79f7d506
// Copyright (c) 2016 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "filesystem.h"
#include "test.h"
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
namespace
kj
{
namespace
{
static
Own
<
File
>
newTempFile
()
{
char
filename
[]
=
"/var/tmp/kj-filesystem-test.XXXXXX"
;
int
fd
;
KJ_SYSCALL
(
fd
=
mkstemp
(
filename
));
KJ_DEFER
(
KJ_SYSCALL
(
unlink
(
filename
)));
return
newDiskFile
(
AutoCloseFd
(
fd
));
}
class
TempDir
{
public
:
TempDir
()
:
filename
(
heapString
(
"/var/tmp/kj-filesystem-test.XXXXXX"
))
{
if
(
mkdtemp
(
filename
.
begin
())
==
nullptr
)
{
KJ_FAIL_SYSCALL
(
"mkdtemp"
,
errno
,
filename
);
}
}
Own
<
Directory
>
get
()
{
int
fd
;
KJ_SYSCALL
(
fd
=
open
(
filename
.
cStr
(),
O_RDONLY
));
return
newDiskDirectory
(
AutoCloseFd
(
fd
));
}
~
TempDir
()
noexcept
(
false
)
{
recursiveDelete
(
filename
);
}
private
:
String
filename
;
static
void
recursiveDelete
(
StringPtr
path
)
{
// Recursively delete the temp dir, verifying that no .kj-tmp. files were left over.
{
DIR
*
dir
=
opendir
(
path
.
cStr
());
KJ_ASSERT
(
dir
!=
nullptr
);
KJ_DEFER
(
closedir
(
dir
));
for
(;;)
{
auto
entry
=
readdir
(
dir
);
if
(
entry
==
nullptr
)
break
;
StringPtr
name
=
entry
->
d_name
;
if
(
name
==
"."
||
name
==
".."
)
continue
;
auto
subPath
=
kj
::
str
(
path
,
'/'
,
entry
->
d_name
);
KJ_EXPECT
(
!
name
.
startsWith
(
".kj-tmp."
),
"temp file not cleaned up"
,
subPath
);
struct
stat
stats
;
KJ_SYSCALL
(
lstat
(
subPath
.
cStr
(),
&
stats
));
if
(
S_ISDIR
(
stats
.
st_mode
))
{
recursiveDelete
(
subPath
);
}
else
{
KJ_SYSCALL
(
unlink
(
subPath
.
cStr
()));
}
}
}
KJ_SYSCALL
(
rmdir
(
path
.
cStr
()));
}
};
KJ_TEST
(
"DiskFile"
)
{
auto
file
=
newTempFile
();
KJ_EXPECT
(
file
->
readAllText
()
==
""
);
file
->
writeAll
(
"foo"
);
KJ_EXPECT
(
file
->
readAllText
()
==
"foo"
);
file
->
write
(
3
,
StringPtr
(
"bar"
).
asBytes
());
KJ_EXPECT
(
file
->
readAllText
()
==
"foobar"
);
file
->
write
(
3
,
StringPtr
(
"baz"
).
asBytes
());
KJ_EXPECT
(
file
->
readAllText
()
==
"foobaz"
);
file
->
write
(
9
,
StringPtr
(
"qux"
).
asBytes
());
KJ_EXPECT
(
file
->
readAllText
()
==
kj
::
StringPtr
(
"foobaz
\0\0\0
qux"
,
12
));
file
->
truncate
(
6
);
KJ_EXPECT
(
file
->
readAllText
()
==
"foobaz"
);
file
->
truncate
(
18
);
KJ_EXPECT
(
file
->
readAllText
()
==
kj
::
StringPtr
(
"foobaz
\0\0\0\0\0\0\0\0\0\0\0\0
"
,
18
));
{
auto
mapping
=
file
->
mmap
(
0
,
18
);
auto
privateMapping
=
file
->
mmapPrivate
(
0
,
18
);
auto
writableMapping
=
file
->
mmapWritable
(
0
,
18
);
KJ_EXPECT
(
mapping
.
size
()
==
18
);
KJ_EXPECT
(
privateMapping
.
size
()
==
18
);
KJ_EXPECT
(
writableMapping
->
get
().
size
()
==
18
);
KJ_EXPECT
(
writableMapping
->
get
().
begin
()
!=
mapping
.
begin
());
KJ_EXPECT
(
privateMapping
.
begin
()
!=
mapping
.
begin
());
KJ_EXPECT
(
writableMapping
->
get
().
begin
()
!=
privateMapping
.
begin
());
KJ_EXPECT
(
kj
::
str
(
mapping
.
slice
(
0
,
6
).
asChars
())
==
"foobaz"
);
KJ_EXPECT
(
kj
::
str
(
writableMapping
->
get
().
slice
(
0
,
6
).
asChars
())
==
"foobaz"
);
KJ_EXPECT
(
kj
::
str
(
privateMapping
.
slice
(
0
,
6
).
asChars
())
==
"foobaz"
);
privateMapping
[
0
]
=
'F'
;
KJ_EXPECT
(
kj
::
str
(
mapping
.
slice
(
0
,
6
).
asChars
())
==
"foobaz"
);
KJ_EXPECT
(
kj
::
str
(
writableMapping
->
get
().
slice
(
0
,
6
).
asChars
())
==
"foobaz"
);
KJ_EXPECT
(
kj
::
str
(
privateMapping
.
slice
(
0
,
6
).
asChars
())
==
"Foobaz"
);
writableMapping
->
get
()[
0
]
=
'D'
;
writableMapping
->
changed
(
writableMapping
->
get
().
slice
(
0
,
1
));
KJ_EXPECT
(
kj
::
str
(
mapping
.
slice
(
0
,
6
).
asChars
())
==
"Doobaz"
);
KJ_EXPECT
(
kj
::
str
(
writableMapping
->
get
().
slice
(
0
,
6
).
asChars
())
==
"Doobaz"
);
KJ_EXPECT
(
kj
::
str
(
privateMapping
.
slice
(
0
,
6
).
asChars
())
==
"Foobaz"
);
file
->
write
(
0
,
StringPtr
(
"qux"
).
asBytes
());
KJ_EXPECT
(
kj
::
str
(
mapping
.
slice
(
0
,
6
).
asChars
())
==
"quxbaz"
);
KJ_EXPECT
(
kj
::
str
(
writableMapping
->
get
().
slice
(
0
,
6
).
asChars
())
==
"quxbaz"
);
KJ_EXPECT
(
kj
::
str
(
privateMapping
.
slice
(
0
,
6
).
asChars
())
==
"Foobaz"
);
file
->
write
(
12
,
StringPtr
(
"corge"
).
asBytes
());
KJ_EXPECT
(
kj
::
str
(
mapping
.
slice
(
12
,
17
).
asChars
())
==
"corge"
);
// Can shrink.
file
->
truncate
(
6
);
KJ_EXPECT
(
kj
::
str
(
mapping
.
slice
(
12
,
17
).
asChars
())
==
kj
::
StringPtr
(
"
\0\0\0\0\0
"
,
5
));
// Can regrow.
file
->
truncate
(
18
);
KJ_EXPECT
(
kj
::
str
(
mapping
.
slice
(
12
,
17
).
asChars
())
==
kj
::
StringPtr
(
"
\0\0\0\0\0
"
,
5
));
// Can even regrow past previous capacity.
file
->
truncate
(
100
);
}
file
->
truncate
(
6
);
KJ_EXPECT
(
file
->
readAllText
()
==
"quxbaz"
);
file
->
zero
(
3
,
3
);
KJ_EXPECT
(
file
->
readAllText
()
==
StringPtr
(
"qux
\0\0\0
"
,
6
));
}
KJ_TEST
(
"DiskFile::copy()"
)
{
auto
source
=
newTempFile
();
source
->
writeAll
(
"foobarbaz"
);
auto
dest
=
newTempFile
();
dest
->
writeAll
(
"quxcorge"
);
KJ_EXPECT
(
dest
->
copy
(
3
,
*
source
,
6
,
kj
::
maxValue
)
==
3
);
KJ_EXPECT
(
dest
->
readAllText
()
==
"quxbazge"
);
KJ_EXPECT
(
dest
->
copy
(
0
,
*
source
,
3
,
4
)
==
4
);
KJ_EXPECT
(
dest
->
readAllText
()
==
"barbazge"
);
KJ_EXPECT
(
dest
->
copy
(
0
,
*
source
,
128
,
kj
::
maxValue
)
==
0
);
KJ_EXPECT
(
dest
->
copy
(
4
,
*
source
,
3
,
0
)
==
0
);
String
bigString
=
strArray
(
repeat
(
"foobar"
,
10000
),
""
);
source
->
truncate
(
bigString
.
size
()
+
1000
);
source
->
write
(
123
,
bigString
.
asBytes
());
dest
->
copy
(
321
,
*
source
,
123
,
bigString
.
size
());
KJ_EXPECT
(
dest
->
readAllText
().
slice
(
321
)
==
bigString
);
}
KJ_TEST
(
"DiskDirectory"
)
{
TempDir
tempDir
;
auto
dir
=
tempDir
.
get
();
KJ_EXPECT
(
dir
->
listNames
()
==
nullptr
);
KJ_EXPECT
(
dir
->
listEntries
()
==
nullptr
);
KJ_EXPECT
(
!
dir
->
exists
(
Path
(
"foo"
)));
KJ_EXPECT
(
dir
->
tryOpenFile
(
Path
(
"foo"
))
==
nullptr
);
KJ_EXPECT
(
dir
->
tryOpenFile
(
Path
(
"foo"
),
WriteMode
::
MODIFY
)
==
nullptr
);
{
auto
file
=
dir
->
openFile
(
Path
(
"foo"
),
WriteMode
::
CREATE
);
file
->
writeAll
(
"foobar"
);
}
KJ_EXPECT
(
dir
->
exists
(
Path
(
"foo"
)));
{
auto
stats
=
dir
->
lstat
(
Path
(
"foo"
));
KJ_EXPECT
(
stats
.
type
==
FsNode
::
Type
::
FILE
);
KJ_EXPECT
(
stats
.
size
==
6
);
}
{
auto
list
=
dir
->
listNames
();
KJ_ASSERT
(
list
.
size
()
==
1
);
KJ_EXPECT
(
list
[
0
]
==
"foo"
);
}
{
auto
list
=
dir
->
listEntries
();
KJ_ASSERT
(
list
.
size
()
==
1
);
KJ_EXPECT
(
list
[
0
].
name
==
"foo"
);
KJ_EXPECT
(
list
[
0
].
type
==
FsNode
::
Type
::
FILE
);
}
KJ_EXPECT
(
dir
->
openFile
(
Path
(
"foo"
))
->
readAllText
()
==
"foobar"
);
KJ_EXPECT
(
dir
->
tryOpenFile
(
Path
({
"foo"
,
"bar"
}),
WriteMode
::
MODIFY
)
==
nullptr
);
KJ_EXPECT
(
dir
->
tryOpenFile
(
Path
({
"bar"
,
"baz"
}),
WriteMode
::
MODIFY
)
==
nullptr
);
KJ_EXPECT_THROW_MESSAGE
(
"parent is not a directory"
,
dir
->
tryOpenFile
(
Path
({
"bar"
,
"baz"
}),
WriteMode
::
CREATE
));
{
auto
file
=
dir
->
openFile
(
Path
({
"bar"
,
"baz"
}),
WriteMode
::
CREATE
|
WriteMode
::
CREATE_PARENT
);
file
->
writeAll
(
"bazqux"
);
}
KJ_EXPECT
(
dir
->
openFile
(
Path
({
"bar"
,
"baz"
}))
->
readAllText
()
==
"bazqux"
);
{
auto
stats
=
dir
->
lstat
(
Path
(
"bar"
));
KJ_EXPECT
(
stats
.
type
==
FsNode
::
Type
::
DIRECTORY
);
}
{
auto
list
=
dir
->
listNames
();
KJ_ASSERT
(
list
.
size
()
==
2
);
KJ_EXPECT
(
list
[
0
]
==
"bar"
);
KJ_EXPECT
(
list
[
1
]
==
"foo"
);
}
{
auto
list
=
dir
->
listEntries
();
KJ_ASSERT
(
list
.
size
()
==
2
);
KJ_EXPECT
(
list
[
0
].
name
==
"bar"
);
KJ_EXPECT
(
list
[
0
].
type
==
FsNode
::
Type
::
DIRECTORY
);
KJ_EXPECT
(
list
[
1
].
name
==
"foo"
);
KJ_EXPECT
(
list
[
1
].
type
==
FsNode
::
Type
::
FILE
);
}
{
auto
subdir
=
dir
->
openSubdir
(
Path
(
"bar"
));
KJ_EXPECT
(
subdir
->
openFile
(
Path
(
"baz"
))
->
readAllText
()
==
"bazqux"
);
}
auto
subdir
=
dir
->
openSubdir
(
Path
(
"corge"
),
WriteMode
::
CREATE
);
subdir
->
openFile
(
Path
(
"grault"
),
WriteMode
::
CREATE
)
->
writeAll
(
"garply"
);
KJ_EXPECT
(
dir
->
openFile
(
Path
({
"corge"
,
"grault"
}))
->
readAllText
()
==
"garply"
);
dir
->
openFile
(
Path
({
"corge"
,
"grault"
}),
WriteMode
::
CREATE
|
WriteMode
::
MODIFY
)
->
write
(
0
,
StringPtr
(
"rag"
).
asBytes
());
KJ_EXPECT
(
dir
->
openFile
(
Path
({
"corge"
,
"grault"
}))
->
readAllText
()
==
"ragply"
);
KJ_EXPECT
(
dir
->
openSubdir
(
Path
(
"corge"
))
->
listNames
().
size
()
==
1
);
{
auto
replacer
=
dir
->
replaceFile
(
Path
({
"corge"
,
"grault"
}),
WriteMode
::
CREATE
|
WriteMode
::
MODIFY
);
replacer
->
get
().
writeAll
(
"rag"
);
// temp file not in list
KJ_EXPECT
(
dir
->
openSubdir
(
Path
(
"corge"
))
->
listNames
().
size
()
==
1
);
// Don't commit.
}
KJ_EXPECT
(
dir
->
openFile
(
Path
({
"corge"
,
"grault"
}))
->
readAllText
()
==
"ragply"
);
{
auto
replacer
=
dir
->
replaceFile
(
Path
({
"corge"
,
"grault"
}),
WriteMode
::
CREATE
|
WriteMode
::
MODIFY
);
replacer
->
get
().
writeAll
(
"rag"
);
// temp file not in list
KJ_EXPECT
(
dir
->
openSubdir
(
Path
(
"corge"
))
->
listNames
().
size
()
==
1
);
replacer
->
commit
();
KJ_EXPECT
(
dir
->
openFile
(
Path
({
"corge"
,
"grault"
}))
->
readAllText
()
==
"rag"
);
}
KJ_EXPECT
(
dir
->
openFile
(
Path
({
"corge"
,
"grault"
}))
->
readAllText
()
==
"rag"
);
{
auto
appender
=
dir
->
appendFile
(
Path
({
"corge"
,
"grault"
}),
WriteMode
::
MODIFY
);
appender
->
write
(
"waldo"
,
5
);
appender
->
write
(
"fred"
,
4
);
}
KJ_EXPECT
(
dir
->
openFile
(
Path
({
"corge"
,
"grault"
}))
->
readAllText
()
==
"ragwaldofred"
);
KJ_EXPECT
(
dir
->
exists
(
Path
(
"foo"
)));
dir
->
remove
(
Path
(
"foo"
));
KJ_EXPECT
(
!
dir
->
exists
(
Path
(
"foo"
)));
KJ_EXPECT
(
!
dir
->
tryRemove
(
Path
(
"foo"
)));
KJ_EXPECT
(
dir
->
exists
(
Path
({
"bar"
,
"baz"
})));
dir
->
remove
(
Path
({
"bar"
,
"baz"
}));
KJ_EXPECT
(
!
dir
->
exists
(
Path
({
"bar"
,
"baz"
})));
KJ_EXPECT
(
dir
->
exists
(
Path
(
"bar"
)));
KJ_EXPECT
(
!
dir
->
tryRemove
(
Path
({
"bar"
,
"baz"
})));
KJ_EXPECT
(
dir
->
exists
(
Path
(
"corge"
)));
KJ_EXPECT
(
dir
->
exists
(
Path
({
"corge"
,
"grault"
})));
dir
->
remove
(
Path
(
"corge"
));
KJ_EXPECT
(
!
dir
->
exists
(
Path
(
"corge"
)));
KJ_EXPECT
(
!
dir
->
exists
(
Path
({
"corge"
,
"grault"
})));
KJ_EXPECT
(
!
dir
->
tryRemove
(
Path
(
"corge"
)));
}
KJ_TEST
(
"DiskDirectory symlinks"
)
{
TempDir
tempDir
;
auto
dir
=
tempDir
.
get
();
dir
->
symlink
(
Path
(
"foo"
),
"bar/qux/../baz"
,
WriteMode
::
CREATE
);
KJ_EXPECT
(
!
dir
->
trySymlink
(
Path
(
"foo"
),
"bar/qux/../baz"
,
WriteMode
::
CREATE
));
{
auto
stats
=
dir
->
lstat
(
Path
(
"foo"
));
KJ_EXPECT
(
stats
.
type
==
FsNode
::
Type
::
SYMLINK
);
}
KJ_EXPECT
(
dir
->
readlink
(
Path
(
"foo"
))
==
"bar/qux/../baz"
);
// Broken link into non-existing directory cannot be opened in any mode.
KJ_EXPECT
(
dir
->
tryOpenFile
(
Path
(
"foo"
))
==
nullptr
);
KJ_EXPECT
(
dir
->
tryOpenFile
(
Path
(
"foo"
),
WriteMode
::
CREATE
)
==
nullptr
);
KJ_EXPECT
(
dir
->
tryOpenFile
(
Path
(
"foo"
),
WriteMode
::
MODIFY
)
==
nullptr
);
KJ_EXPECT_THROW_MESSAGE
(
"parent is not a directory"
,
dir
->
tryOpenFile
(
Path
(
"foo"
),
WriteMode
::
CREATE
|
WriteMode
::
MODIFY
));
KJ_EXPECT_THROW_MESSAGE
(
"parent is not a directory"
,
dir
->
tryOpenFile
(
Path
(
"foo"
),
WriteMode
::
CREATE
|
WriteMode
::
MODIFY
|
WriteMode
::
CREATE_PARENT
));
// Create the directory.
auto
subdir
=
dir
->
openSubdir
(
Path
(
"bar"
),
WriteMode
::
CREATE
);
subdir
->
openSubdir
(
Path
(
"qux"
),
WriteMode
::
CREATE
);
// Link still points to non-existing file so cannot be open in most modes.
KJ_EXPECT
(
dir
->
tryOpenFile
(
Path
(
"foo"
))
==
nullptr
);
KJ_EXPECT
(
dir
->
tryOpenFile
(
Path
(
"foo"
),
WriteMode
::
CREATE
)
==
nullptr
);
KJ_EXPECT
(
dir
->
tryOpenFile
(
Path
(
"foo"
),
WriteMode
::
MODIFY
)
==
nullptr
);
// But... CREATE | MODIFY works.
dir
->
openFile
(
Path
(
"foo"
),
WriteMode
::
CREATE
|
WriteMode
::
MODIFY
)
->
writeAll
(
"foobar"
);
KJ_EXPECT
(
dir
->
openFile
(
Path
({
"bar"
,
"baz"
}))
->
readAllText
()
==
"foobar"
);
KJ_EXPECT
(
dir
->
openFile
(
Path
(
"foo"
))
->
readAllText
()
==
"foobar"
);
KJ_EXPECT
(
dir
->
openFile
(
Path
(
"foo"
),
WriteMode
::
MODIFY
)
->
readAllText
()
==
"foobar"
);
// operations that modify the symlink
dir
->
symlink
(
Path
(
"foo"
),
"corge"
,
WriteMode
::
MODIFY
);
KJ_EXPECT
(
dir
->
openFile
(
Path
({
"bar"
,
"baz"
}))
->
readAllText
()
==
"foobar"
);
KJ_EXPECT
(
dir
->
readlink
(
Path
(
"foo"
))
==
"corge"
);
KJ_EXPECT
(
!
dir
->
exists
(
Path
(
"foo"
)));
KJ_EXPECT
(
dir
->
lstat
(
Path
(
"foo"
)).
type
==
FsNode
::
Type
::
SYMLINK
);
KJ_EXPECT
(
dir
->
tryOpenFile
(
Path
(
"foo"
))
==
nullptr
);
dir
->
remove
(
Path
(
"foo"
));
KJ_EXPECT
(
!
dir
->
exists
(
Path
(
"foo"
)));
KJ_EXPECT
(
dir
->
tryOpenFile
(
Path
(
"foo"
))
==
nullptr
);
}
KJ_TEST
(
"DiskDirectory link"
)
{
TempDir
tempDirSrc
;
TempDir
tempDirDst
;
auto
src
=
tempDirSrc
.
get
();
auto
dst
=
tempDirDst
.
get
();
src
->
openFile
(
Path
(
"foo"
),
WriteMode
::
CREATE
|
WriteMode
::
CREATE_PARENT
)
->
writeAll
(
"foobar"
);
dst
->
transfer
(
Path
(
"link"
),
WriteMode
::
CREATE
,
*
src
,
Path
(
"foo"
),
TransferMode
::
LINK
);
KJ_EXPECT
(
dst
->
openFile
(
Path
(
"link"
))
->
readAllText
()
==
"foobar"
);
// Writing the old location modifies the new.
src
->
openFile
(
Path
(
"foo"
),
WriteMode
::
MODIFY
)
->
writeAll
(
"bazqux"
);
KJ_EXPECT
(
dst
->
openFile
(
Path
(
"link"
))
->
readAllText
()
==
"bazqux"
);
// Replacing the old location doesn't modify the new.
{
auto
replacer
=
src
->
replaceFile
(
Path
(
"foo"
),
WriteMode
::
MODIFY
);
replacer
->
get
().
writeAll
(
"corge"
);
replacer
->
commit
();
}
KJ_EXPECT
(
src
->
openFile
(
Path
(
"foo"
))
->
readAllText
()
==
"corge"
);
KJ_EXPECT
(
dst
->
openFile
(
Path
(
"link"
))
->
readAllText
()
==
"bazqux"
);
}
KJ_TEST
(
"DiskDirectory copy"
)
{
TempDir
tempDirSrc
;
TempDir
tempDirDst
;
auto
src
=
tempDirSrc
.
get
();
auto
dst
=
tempDirDst
.
get
();
src
->
openFile
(
Path
({
"foo"
,
"bar"
}),
WriteMode
::
CREATE
|
WriteMode
::
CREATE_PARENT
)
->
writeAll
(
"foobar"
);
src
->
openFile
(
Path
({
"foo"
,
"baz"
,
"qux"
}),
WriteMode
::
CREATE
|
WriteMode
::
CREATE_PARENT
)
->
writeAll
(
"bazqux"
);
dst
->
transfer
(
Path
(
"link"
),
WriteMode
::
CREATE
,
*
src
,
Path
(
"foo"
),
TransferMode
::
COPY
);
KJ_EXPECT
(
src
->
openFile
(
Path
({
"foo"
,
"bar"
}))
->
readAllText
()
==
"foobar"
);
KJ_EXPECT
(
src
->
openFile
(
Path
({
"foo"
,
"baz"
,
"qux"
}))
->
readAllText
()
==
"bazqux"
);
KJ_EXPECT
(
dst
->
openFile
(
Path
({
"link"
,
"bar"
}))
->
readAllText
()
==
"foobar"
);
KJ_EXPECT
(
dst
->
openFile
(
Path
({
"link"
,
"baz"
,
"qux"
}))
->
readAllText
()
==
"bazqux"
);
KJ_EXPECT
(
dst
->
exists
(
Path
({
"link"
,
"bar"
})));
src
->
remove
(
Path
({
"foo"
,
"bar"
}));
KJ_EXPECT
(
dst
->
openFile
(
Path
({
"link"
,
"bar"
}))
->
readAllText
()
==
"foobar"
);
}
KJ_TEST
(
"DiskDirectory move"
)
{
TempDir
tempDirSrc
;
TempDir
tempDirDst
;
auto
src
=
tempDirSrc
.
get
();
auto
dst
=
tempDirDst
.
get
();
src
->
openFile
(
Path
({
"foo"
,
"bar"
}),
WriteMode
::
CREATE
|
WriteMode
::
CREATE_PARENT
)
->
writeAll
(
"foobar"
);
src
->
openFile
(
Path
({
"foo"
,
"baz"
,
"qux"
}),
WriteMode
::
CREATE
|
WriteMode
::
CREATE_PARENT
)
->
writeAll
(
"bazqux"
);
dst
->
transfer
(
Path
(
"link"
),
WriteMode
::
CREATE
,
*
src
,
Path
(
"foo"
),
TransferMode
::
MOVE
);
KJ_EXPECT
(
!
src
->
exists
(
Path
({
"foo"
})));
KJ_EXPECT
(
dst
->
openFile
(
Path
({
"link"
,
"bar"
}))
->
readAllText
()
==
"foobar"
);
KJ_EXPECT
(
dst
->
openFile
(
Path
({
"link"
,
"baz"
,
"qux"
}))
->
readAllText
()
==
"bazqux"
);
}
KJ_TEST
(
"DiskDirectory createTemporary"
)
{
TempDir
tempDir
;
auto
dir
=
tempDir
.
get
();
auto
file
=
dir
->
createTemporary
();
file
->
writeAll
(
"foobar"
);
KJ_EXPECT
(
file
->
readAllText
()
==
"foobar"
);
KJ_EXPECT
(
dir
->
listNames
()
==
nullptr
);
}
KJ_TEST
(
"DiskDirectory replace directory with file"
)
{
TempDir
tempDir
;
auto
dir
=
tempDir
.
get
();
dir
->
openFile
(
Path
({
"foo"
,
"bar"
}),
WriteMode
::
CREATE
|
WriteMode
::
CREATE_PARENT
)
->
writeAll
(
"foobar"
);
{
// CREATE fails -- already exists.
auto
replacer
=
dir
->
replaceFile
(
Path
(
"foo"
),
WriteMode
::
CREATE
);
replacer
->
get
().
writeAll
(
"bazqux"
);
KJ_EXPECT
(
!
replacer
->
tryCommit
());
}
// Still a directory.
KJ_EXPECT
(
dir
->
lstat
(
Path
(
"foo"
)).
type
==
FsNode
::
Type
::
DIRECTORY
);
{
// MODIFY succeeds.
auto
replacer
=
dir
->
replaceFile
(
Path
(
"foo"
),
WriteMode
::
MODIFY
);
replacer
->
get
().
writeAll
(
"bazqux"
);
replacer
->
commit
();
}
// Replaced with file.
KJ_EXPECT
(
dir
->
openFile
(
Path
(
"foo"
))
->
readAllText
()
==
"bazqux"
);
}
KJ_TEST
(
"DiskDirectory replace file with directory"
)
{
TempDir
tempDir
;
auto
dir
=
tempDir
.
get
();
dir
->
openFile
(
Path
(
"foo"
),
WriteMode
::
CREATE
)
->
writeAll
(
"foobar"
);
{
// CREATE fails -- already exists.
auto
replacer
=
dir
->
replaceSubdir
(
Path
(
"foo"
),
WriteMode
::
CREATE
);
replacer
->
get
().
openFile
(
Path
(
"bar"
),
WriteMode
::
CREATE
)
->
writeAll
(
"bazqux"
);
KJ_EXPECT
(
dir
->
listNames
().
size
()
==
1
&&
dir
->
listNames
()[
0
]
==
"foo"
);
KJ_EXPECT
(
!
replacer
->
tryCommit
());
}
// Still a file.
KJ_EXPECT
(
dir
->
openFile
(
Path
(
"foo"
))
->
readAllText
()
==
"foobar"
);
{
// MODIFY succeeds.
auto
replacer
=
dir
->
replaceSubdir
(
Path
(
"foo"
),
WriteMode
::
MODIFY
);
replacer
->
get
().
openFile
(
Path
(
"bar"
),
WriteMode
::
CREATE
)
->
writeAll
(
"bazqux"
);
KJ_EXPECT
(
dir
->
listNames
().
size
()
==
1
&&
dir
->
listNames
()[
0
]
==
"foo"
);
replacer
->
commit
();
}
// Replaced with directory.
KJ_EXPECT
(
dir
->
openFile
(
Path
({
"foo"
,
"bar"
}))
->
readAllText
()
==
"bazqux"
);
}
#ifndef HOLES_NOT_SUPPORTED
KJ_TEST
(
"DiskFile holes"
)
{
TempDir
tempDir
;
auto
dir
=
tempDir
.
get
();
auto
file
=
dir
->
openFile
(
Path
(
"holes"
),
WriteMode
::
CREATE
);
file
->
writeAll
(
"foobar"
);
file
->
write
(
1
<<
20
,
StringPtr
(
"foobar"
).
asBytes
());
// Allow for block sizes as low as 512 bytes and as high as 64k.
auto
meta
=
file
->
stat
();
KJ_EXPECT
(
meta
.
spaceUsed
>=
2
*
512
);
KJ_EXPECT
(
meta
.
spaceUsed
<=
2
*
65536
);
byte
buf
[
7
];
{
// Copy doesn't fill in holes.
dir
->
transfer
(
Path
(
"copy"
),
WriteMode
::
CREATE
,
Path
(
"holes"
),
TransferMode
::
COPY
);
auto
copy
=
dir
->
openFile
(
Path
(
"copy"
));
KJ_EXPECT
(
copy
->
stat
().
spaceUsed
==
meta
.
spaceUsed
);
KJ_EXPECT
(
copy
->
read
(
0
,
buf
)
==
7
);
KJ_EXPECT
(
StringPtr
(
reinterpret_cast
<
char
*>
(
buf
),
6
)
==
"foobar"
);
KJ_EXPECT
(
copy
->
read
(
1
<<
20
,
buf
)
==
6
);
KJ_EXPECT
(
StringPtr
(
reinterpret_cast
<
char
*>
(
buf
),
6
)
==
"foobar"
);
KJ_EXPECT
(
copy
->
read
(
1
<<
19
,
buf
)
==
7
);
KJ_EXPECT
(
StringPtr
(
reinterpret_cast
<
char
*>
(
buf
),
6
)
==
StringPtr
(
"
\0\0\0\0\0\0
"
,
6
));
}
file
->
truncate
(
1
<<
21
);
KJ_EXPECT
(
file
->
stat
().
spaceUsed
==
meta
.
spaceUsed
);
KJ_EXPECT
(
file
->
read
(
1
<<
20
,
buf
)
==
7
);
KJ_EXPECT
(
StringPtr
(
reinterpret_cast
<
char
*>
(
buf
),
6
)
==
"foobar"
);
{
dir
->
transfer
(
Path
(
"copy"
),
WriteMode
::
MODIFY
,
Path
(
"holes"
),
TransferMode
::
COPY
);
auto
copy
=
dir
->
openFile
(
Path
(
"copy"
));
KJ_EXPECT
(
copy
->
stat
().
spaceUsed
==
meta
.
spaceUsed
);
KJ_EXPECT
(
copy
->
read
(
0
,
buf
)
==
7
);
KJ_EXPECT
(
StringPtr
(
reinterpret_cast
<
char
*>
(
buf
),
6
)
==
"foobar"
);
KJ_EXPECT
(
copy
->
read
(
1
<<
20
,
buf
)
==
7
);
KJ_EXPECT
(
StringPtr
(
reinterpret_cast
<
char
*>
(
buf
),
6
)
==
"foobar"
);
KJ_EXPECT
(
copy
->
read
(
1
<<
19
,
buf
)
==
7
);
KJ_EXPECT
(
StringPtr
(
reinterpret_cast
<
char
*>
(
buf
),
6
)
==
StringPtr
(
"
\0\0\0\0\0\0
"
,
6
));
}
}
#endif
}
// namespace
}
// namespace kj
c++/src/kj/filesystem-disk.c++
0 → 100644
View file @
79f7d506
// Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "filesystem.h"
#include "debug.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <errno.h>
#include <dirent.h>
#include <syscall.h>
#include "vector.h"
#include "miniposix.h"
#if __linux__
#include <linux/fs.h>
#include <sys/sendfile.h>
#endif
namespace
kj
{
namespace
{
#define HIDDEN_PREFIX ".kj-tmp."
// Prefix for temp files which should be hidden when listing a directory.
//
// If you change this, make sure to update the unit test.
#ifdef O_CLOEXEC
#define MAYBE_O_CLOEXEC O_CLOEXEC
#else
#define MAYBE_O_CLOEXEC 0
#endif
#ifdef O_DIRECTORY
#define MAYBE_O_DIRECTORY O_DIRECTORY
#else
#define MAYBE_O_DIRECTORY 0
#endif
static
void
setCloexec
(
int
fd
)
KJ_UNUSED
;
static
void
setCloexec
(
int
fd
)
{
// Set the O_CLOEXEC flag on the given fd.
//
// We try to avoid the need to call this by taking advantage of syscall flags that set it
// atomically on new file descriptors. Unfortunately some platforms do not support such syscalls.
#ifdef FIOCLEX
// Yay, we can set the flag in one call.
KJ_SYSCALL_HANDLE_ERRORS
(
ioctl
(
fd
,
FIOCLEX
))
{
case
EINVAL
:
case
EOPNOTSUPP
:
break
;
default:
KJ_FAIL_SYSCALL
(
"ioctl(fd, FIOCLEX)"
,
error
)
{
break
;
}
break
;
}
else
{
// success
return
;
}
#endif
// Sadness, we must resort to read/modify/write.
//
// (On many platforms, FD_CLOEXEC is the only flag modifiable via F_SETFD and therefore we could
// skip the read... but it seems dangerous to assume that's true of all platforms, and anyway
// most platforms support FIOCLEX.)
int
flags
;
KJ_SYSCALL
(
flags
=
fcntl
(
fd
,
F_GETFD
));
if
(
!
(
flags
&
FD_CLOEXEC
))
{
KJ_SYSCALL
(
fcntl
(
fd
,
F_SETFD
,
flags
|
FD_CLOEXEC
));
}
}
static
Date
toKjDate
(
struct
timespec
tv
)
{
return
tv
.
tv_sec
*
SECONDS
+
tv
.
tv_nsec
*
NANOSECONDS
+
UNIX_EPOCH
;
}
static
FsNode
::
Type
modeToType
(
mode_t
mode
)
{
switch
(
mode
&
S_IFMT
)
{
case
S_IFREG
:
return
FsNode
::
Type
::
FILE
;
case
S_IFDIR
:
return
FsNode
::
Type
::
DIRECTORY
;
case
S_IFLNK
:
return
FsNode
::
Type
::
SYMLINK
;
case
S_IFBLK
:
return
FsNode
::
Type
::
BLOCK_DEVICE
;
case
S_IFCHR
:
return
FsNode
::
Type
::
CHARACTER_DEVICE
;
case
S_IFIFO
:
return
FsNode
::
Type
::
NAMED_PIPE
;
case
S_IFSOCK
:
return
FsNode
::
Type
::
SOCKET
;
default
:
return
FsNode
::
Type
::
OTHER
;
}
}
static
FsNode
::
Metadata
statToMetadata
(
struct
stat
&
stats
)
{
return
FsNode
::
Metadata
{
modeToType
(
stats
.
st_mode
),
implicitCast
<
uint64_t
>
(
stats
.
st_size
),
implicitCast
<
uint64_t
>
(
stats
.
st_blocks
*
512u
),
toKjDate
(
stats
.
st_mtim
),
implicitCast
<
uint
>
(
stats
.
st_nlink
)
};
}
static
bool
rmrf
(
int
fd
,
StringPtr
path
);
static
void
rmrfChildrenAndClose
(
int
fd
)
{
// Assumes fd is seeked to beginning.
DIR
*
dir
=
fdopendir
(
fd
);
if
(
dir
==
nullptr
)
{
close
(
fd
);
KJ_FAIL_SYSCALL
(
"fdopendir"
,
errno
);
};
KJ_DEFER
(
closedir
(
dir
));
for
(;;)
{
errno
=
0
;
struct
dirent
*
entry
=
readdir
(
dir
);
if
(
entry
==
nullptr
)
{
int
error
=
errno
;
if
(
error
==
0
)
{
break
;
}
else
{
KJ_FAIL_SYSCALL
(
"readdir"
,
error
);
}
}
if
(
entry
->
d_name
[
0
]
==
'.'
&&
(
entry
->
d_name
[
1
]
==
'\0'
||
(
entry
->
d_name
[
1
]
==
'.'
&&
entry
->
d_name
[
2
]
==
'\0'
)))
{
// ignore . and ..
}
else
{
#ifdef DT_UNKNOWN // d_type is not available on all platforms.
if
(
entry
->
d_type
==
DT_DIR
)
{
int
subdirFd
;
KJ_SYSCALL
(
subdirFd
=
openat
(
fd
,
entry
->
d_name
,
O_RDONLY
|
MAYBE_O_DIRECTORY
|
MAYBE_O_CLOEXEC
));
rmrfChildrenAndClose
(
subdirFd
);
KJ_SYSCALL
(
unlinkat
(
fd
,
entry
->
d_name
,
AT_REMOVEDIR
));
}
else
if
(
entry
->
d_type
!=
DT_UNKNOWN
)
{
KJ_SYSCALL
(
unlinkat
(
fd
,
entry
->
d_name
,
0
));
}
else
{
#endif
KJ_ASSERT
(
rmrf
(
fd
,
entry
->
d_name
));
#ifdef DT_UNKNOWN
}
#endif
}
}
}
static
bool
rmrf
(
int
fd
,
StringPtr
path
)
{
struct
stat
stats
;
KJ_SYSCALL_HANDLE_ERRORS
(
fstatat
(
fd
,
path
.
cStr
(),
&
stats
,
AT_SYMLINK_NOFOLLOW
))
{
case
ENOENT
:
case
ENOTDIR
:
// Doesn't exist.
return
false
;
default:
KJ_FAIL_SYSCALL
(
"lstat(path)"
,
error
,
path
)
{
return
false
;
}
}
if
(
S_ISDIR
(
stats
.
st_mode
))
{
int
subdirFd
;
KJ_SYSCALL
(
subdirFd
=
openat
(
fd
,
path
.
cStr
(),
O_RDONLY
|
MAYBE_O_DIRECTORY
|
MAYBE_O_CLOEXEC
))
{
return
false
;
}
rmrfChildrenAndClose
(
subdirFd
);
KJ_SYSCALL
(
unlinkat
(
fd
,
path
.
cStr
(),
AT_REMOVEDIR
))
{
return
false
;
}
}
else
{
KJ_SYSCALL
(
unlinkat
(
fd
,
path
.
cStr
(),
0
))
{
return
false
;
}
}
return
true
;
}
struct
MmapRange
{
uint64_t
offset
;
uint64_t
size
;
};
static
MmapRange
getMmapRange
(
uint64_t
offset
,
uint64_t
size
)
{
// Rounds the given byte range up to page boundaries.
#ifndef _SC_PAGESIZE
#define _SC_PAGESIZE _SC_PAGE_SIZE
#endif
static
const
uint64_t
pageSize
=
sysconf
(
_SC_PAGESIZE
);
uint64_t
pageMask
=
pageSize
-
1
;
uint64_t
realOffset
=
offset
&
~
pageMask
;
uint64_t
end
=
offset
+
size
;
uint64_t
realEnd
=
(
end
+
pageMask
)
&
~
pageMask
;
return
{
realOffset
,
realEnd
-
realOffset
};
}
class
MmapDisposer
:
public
ArrayDisposer
{
protected
:
void
disposeImpl
(
void
*
firstElement
,
size_t
elementSize
,
size_t
elementCount
,
size_t
capacity
,
void
(
*
destroyElement
)(
void
*
))
const
{
auto
range
=
getMmapRange
(
reinterpret_cast
<
uintptr_t
>
(
firstElement
),
elementSize
*
elementCount
);
#if _WIN32
KJ_ASSERT
(
UnmapViewOfFile
(
reinterpret_cast
<
byte
*>
(
range
.
offset
)))
{
break
;
}
#else
KJ_SYSCALL
(
munmap
(
reinterpret_cast
<
byte
*>
(
range
.
offset
),
range
.
size
))
{
break
;
}
#endif
}
};
constexpr
MmapDisposer
mmapDisposer
=
MmapDisposer
();
class
DiskHandle
{
// We need to implement each of ReadableFile, AppendableFile, File, ReadableDirectory, and
// Directory for disk handles. There is a lot of implementation overlap between these, especially
// stat(), sync(), etc. We can't have everything inherit from a common DiskFsNode that implements
// these because then we get diamond inheritance which means we need to make all our inheritance
// virtual which means downcasting requires RTTI which violates our goal of supporting compiling
// with no RTTI. So instead we have the DiskHandle class which implements all the methods without
// inheriting anything, and then we have DiskFile, DiskDirectory, etc. hold this and delegate to
// it. Ugly, but works.
public
:
DiskHandle
(
AutoCloseFd
&&
fd
)
:
fd
(
kj
::
mv
(
fd
))
{}
// OsHandle ------------------------------------------------------------------
AutoCloseFd
clone
()
{
int
fd2
;
#ifdef F_DUPFD_CLOEXEC
KJ_SYSCALL_HANDLE_ERRORS
(
fd2
=
fcntl
(
fd
,
F_DUPFD_CLOEXEC
,
3
))
{
case
EINVAL
:
case
EOPNOTSUPP
:
// fall back
break
;
default
:
KJ_FAIL_SYSCALL
(
"fnctl(fd, F_DUPFD_CLOEXEC, 3)"
,
error
)
{
break
;
}
break
;
}
else
{
return
AutoCloseFd
(
fd2
);
}
#endif
KJ_SYSCALL
(
fd2
=
::
dup
(
fd
));
AutoCloseFd
result
(
fd2
);
setCloexec
(
result
);
return
result
;
}
int
getFd
()
{
return
fd
.
get
();
}
// FsNode --------------------------------------------------------------------
FsNode
::
Metadata
stat
()
{
struct
stat
stats
;
KJ_SYSCALL
(
::
fstat
(
fd
,
&
stats
));
return
statToMetadata
(
stats
);
}
void
sync
()
{
KJ_SYSCALL
(
fsync
(
fd
));
}
void
datasync
()
{
KJ_SYSCALL
(
fdatasync
(
fd
));
}
// ReadableFile --------------------------------------------------------------
size_t
read
(
uint64_t
offset
,
ArrayPtr
<
byte
>
buffer
)
{
// pread() probably never returns short reads unless it hits EOF. Unfortunately, though, per
// spec we are not allowed to assume this.
size_t
total
=
0
;
while
(
buffer
.
size
()
>
0
)
{
ssize_t
n
;
KJ_SYSCALL
(
n
=
pread
(
fd
,
buffer
.
begin
(),
buffer
.
size
(),
offset
));
if
(
n
==
0
)
break
;
total
+=
n
;
offset
+=
n
;
buffer
=
buffer
.
slice
(
n
,
buffer
.
size
());
}
return
total
;
}
Array
<
const
byte
>
mmap
(
uint64_t
offset
,
uint64_t
size
)
{
auto
range
=
getMmapRange
(
offset
,
size
);
const
void
*
mapping
=
::
mmap
(
NULL
,
range
.
size
,
PROT_READ
,
MAP_SHARED
,
fd
,
range
.
offset
);
if
(
mapping
==
MAP_FAILED
)
{
KJ_FAIL_SYSCALL
(
"mmap"
,
errno
);
}
return
Array
<
const
byte
>
(
reinterpret_cast
<
const
byte
*>
(
mapping
)
+
(
offset
-
range
.
offset
),
size
,
mmapDisposer
);
}
Array
<
byte
>
mmapPrivate
(
uint64_t
offset
,
uint64_t
size
)
{
auto
range
=
getMmapRange
(
offset
,
size
);
void
*
mapping
=
::
mmap
(
NULL
,
range
.
size
,
PROT_READ
|
PROT_WRITE
,
MAP_PRIVATE
,
fd
,
range
.
offset
);
if
(
mapping
==
MAP_FAILED
)
{
KJ_FAIL_SYSCALL
(
"mmap"
,
errno
);
}
return
Array
<
byte
>
(
reinterpret_cast
<
byte
*>
(
mapping
)
+
(
offset
-
range
.
offset
),
size
,
mmapDisposer
);
}
// File ----------------------------------------------------------------------
void
write
(
uint64_t
offset
,
ArrayPtr
<
const
byte
>
data
)
{
// pwrite() probably never returns short writes unless there's no space left on disk.
// Unfortunately, though, per spec we are not allowed to assume this.
while
(
data
.
size
()
>
0
)
{
ssize_t
n
;
KJ_SYSCALL
(
n
=
pwrite
(
fd
,
data
.
begin
(),
data
.
size
(),
offset
));
KJ_ASSERT
(
n
>
0
,
"pwrite() returned zero?"
);
offset
+=
n
;
data
=
data
.
slice
(
n
,
data
.
size
());
}
}
void
zero
(
uint64_t
offset
,
uint64_t
size
)
{
#ifdef FALLOC_FL_PUNCH_HOLE
KJ_SYSCALL_HANDLE_ERRORS
(
fallocate
(
fd
,
FALLOC_FL_PUNCH_HOLE
|
FALLOC_FL_KEEP_SIZE
,
offset
,
size
))
{
case
EOPNOTSUPP
:
// fall back to below
break
;
default
:
KJ_FAIL_SYSCALL
(
"fallocate(FALLOC_FL_PUNCH_HOLE)"
,
error
)
{
return
;
}
}
else
{
return
;
}
#endif
// Use a 4k buffer of zeros amplified by iov to write zeros with as few syscalls as possible.
byte
buffer
[
4096
];
memset
(
buffer
,
0
,
sizeof
(
buffer
));
size_t
count
=
(
size
+
sizeof
(
buffer
)
-
1
)
/
sizeof
(
buffer
);
const
size_t
iovmax
=
miniposix
::
iovMax
(
count
);
KJ_STACK_ARRAY
(
struct
iovec
,
iov
,
kj
::
min
(
iovmax
,
count
),
16
,
256
);
for
(
auto
&
item
:
iov
)
{
item
.
iov_base
=
buffer
;
item
.
iov_len
=
sizeof
(
buffer
);
}
while
(
size
>
0
)
{
size_t
iovCount
;
if
(
size
>=
iov
.
size
()
*
sizeof
(
buffer
))
{
iovCount
=
iov
.
size
();
}
else
{
iovCount
=
size
/
sizeof
(
buffer
);
size_t
rem
=
size
%
sizeof
(
buffer
);
if
(
rem
>
0
)
{
iov
[
iovCount
++
].
iov_len
=
rem
;
}
}
ssize_t
n
;
KJ_SYSCALL
(
n
=
pwritev
(
fd
,
iov
.
begin
(),
count
,
offset
));
KJ_ASSERT
(
n
>
0
,
"pwrite() returned zero?"
);
offset
+=
n
;
size
-=
n
;
}
}
void
truncate
(
uint64_t
size
)
{
KJ_SYSCALL
(
ftruncate
(
fd
,
size
));
}
class
WritableFileMappingImpl
:
public
WritableFileMapping
{
public
:
WritableFileMappingImpl
(
Array
<
byte
>
bytes
)
:
bytes
(
kj
::
mv
(
bytes
))
{}
ArrayPtr
<
byte
>
get
()
override
{
return
bytes
;
}
void
changed
(
ArrayPtr
<
byte
>
slice
)
override
{
KJ_REQUIRE
(
slice
.
begin
()
>=
bytes
.
begin
()
&&
slice
.
end
()
<=
bytes
.
end
(),
"byte range is not part of this mapping"
);
KJ_SYSCALL
(
msync
(
slice
.
begin
(),
slice
.
size
(),
MS_ASYNC
));
}
void
sync
(
ArrayPtr
<
byte
>
slice
)
override
{
KJ_REQUIRE
(
slice
.
begin
()
>=
bytes
.
begin
()
&&
slice
.
end
()
<=
bytes
.
end
(),
"byte range is not part of this mapping"
);
KJ_SYSCALL
(
msync
(
slice
.
begin
(),
slice
.
size
(),
MS_SYNC
));
}
private
:
Array
<
byte
>
bytes
;
};
Own
<
WritableFileMapping
>
mmapWritable
(
uint64_t
offset
,
uint64_t
size
)
{
auto
range
=
getMmapRange
(
offset
,
size
);
void
*
mapping
=
::
mmap
(
NULL
,
range
.
size
,
PROT_READ
|
PROT_WRITE
,
MAP_SHARED
,
fd
,
range
.
offset
);
if
(
mapping
==
MAP_FAILED
)
{
KJ_FAIL_SYSCALL
(
"mmap"
,
errno
);
}
auto
array
=
Array
<
byte
>
(
reinterpret_cast
<
byte
*>
(
mapping
)
+
(
offset
-
range
.
offset
),
size
,
mmapDisposer
);
return
heap
<
WritableFileMappingImpl
>
(
kj
::
mv
(
array
));
}
size_t
copyChunk
(
uint64_t
offset
,
int
fromFd
,
uint64_t
fromOffset
,
uint64_t
size
)
{
// Copies a range of bytes from `fromFd` to this file in the most efficient way possible for
// the OS. Only returns less than `size` if EOF. Does not account for holes.
#if __linux__
{
KJ_SYSCALL
(
lseek
(
fd
,
offset
,
SEEK_SET
));
off_t
fromPos
=
fromOffset
;
off_t
end
=
fromOffset
+
size
;
while
(
fromPos
<
end
)
{
ssize_t
n
;
KJ_SYSCALL_HANDLE_ERRORS
(
n
=
sendfile
(
fd
,
fromFd
,
&
fromPos
,
end
-
fromPos
))
{
case
EINVAL
:
case
ENOSYS
:
goto
sendfileNotAvailable
;
default
:
KJ_FAIL_SYSCALL
(
"sendfile"
,
error
)
{
return
fromPos
-
fromOffset
;
}
}
}
return
fromPos
-
fromOffset
;
}
sendfileNotAvailable
:
#endif
uint64_t
total
=
0
;
while
(
size
>
0
)
{
byte
buffer
[
4096
];
ssize_t
n
;
KJ_SYSCALL
(
n
=
pread
(
fromFd
,
buffer
,
kj
::
min
(
sizeof
(
buffer
),
size
),
fromOffset
));
if
(
n
==
0
)
break
;
write
(
offset
,
arrayPtr
(
buffer
,
n
));
fromOffset
+=
n
;
offset
+=
n
;
total
+=
n
;
size
-=
n
;
}
return
total
;
}
kj
::
Maybe
<
size_t
>
copy
(
uint64_t
offset
,
ReadableFile
&
from
,
uint64_t
fromOffset
,
uint64_t
size
)
{
KJ_IF_MAYBE
(
otherFd
,
from
.
getFd
())
{
#ifdef FICLONE
if
(
offset
==
0
&&
fromOffset
==
0
&&
size
==
kj
::
maxValue
&&
stat
().
size
==
0
)
{
if
(
ioctl
(
fd
,
FICLONE
,
*
otherFd
)
>=
0
)
{
return
stat
().
size
;
}
}
else
if
(
size
>
0
)
{
// src_length = 0 has special meaning for the syscall, so avoid.
struct
file_clone_range
range
;
memset
(
&
range
,
0
,
sizeof
(
range
));
range
.
src_fd
=
*
otherFd
;
range
.
dest_offset
=
offset
;
range
.
src_offset
=
fromOffset
;
range
.
src_length
=
size
==
kj
::
maxValue
?
0
:
size
;
if
(
ioctl
(
fd
,
FICLONERANGE
,
&
range
)
>=
0
)
{
// TODO(someday): What does FICLONERANGE actually do if the range goes past EOF? The docs
// don't say. Maybe it only copies the parts that exist. Maybe it punches holes for the
// rest. Where does the destination file's EOF marker end up? Who knows?
return
kj
::
min
(
from
.
stat
().
size
-
fromOffset
,
size
);
}
}
else
{
// size == 0
return
size_t
(
0
);
}
// ioctl failed. Almost all failures documented for these are of the form "the operation is
// not supported for the filesystem(s) specified", so fall back to other approaches.
#endif
off_t
toPos
=
offset
;
off_t
fromPos
=
fromOffset
;
off_t
end
=
size
==
kj
::
maxValue
?
off_t
(
kj
::
maxValue
)
:
off_t
(
fromOffset
+
size
);
for
(;;)
{
// Handle data.
{
// Find out how much data there is before the next hole.
off_t
nextHole
;
#ifdef SEEK_HOLE
KJ_SYSCALL_HANDLE_ERRORS
(
nextHole
=
lseek
(
*
otherFd
,
fromPos
,
SEEK_HOLE
))
{
case
EINVAL
:
// SEEK_HOLE probably not supported. Assume no holes.
nextHole
=
end
;
break
;
case
ENXIO
:
// Past EOF. Stop here.
return
fromPos
-
fromOffset
;
default
:
KJ_FAIL_SYSCALL
(
"lseek(fd, pos, SEEK_HOLE)"
,
error
)
{
return
fromPos
-
fromOffset
;
}
}
#else
// SEEK_HOLE not supported. Assume no holes.
nextHole
=
end
;
#endif
// Copy the next chunk of data.
off_t
copyTo
=
kj
::
min
(
end
,
nextHole
);
size_t
amount
=
copyTo
-
fromPos
;
if
(
amount
>
0
)
{
size_t
n
=
copyChunk
(
toPos
,
*
otherFd
,
fromPos
,
amount
);
fromPos
+=
n
;
toPos
+=
n
;
if
(
n
<
amount
)
{
return
fromPos
-
fromOffset
;
}
}
if
(
fromPos
==
end
)
{
return
fromPos
-
fromOffset
;
}
}
#ifdef SEEK_HOLE
// Handle hole.
{
// Find out how much hole there is before the next data.
off_t
nextData
;
KJ_SYSCALL_HANDLE_ERRORS
(
nextData
=
lseek
(
*
otherFd
,
fromPos
,
SEEK_DATA
))
{
case
EINVAL
:
// SEEK_DATA probably not supported. But we should only have gotten here if we
// were expecting a hole.
KJ_FAIL_ASSERT
(
"can't determine hole size; SEEK_DATA not supported"
);
break
;
case
ENXIO
:
// No more data. Set to EOF.
KJ_SYSCALL
(
nextData
=
lseek
(
*
otherFd
,
0
,
SEEK_END
));
if
(
nextData
>
end
)
{
end
=
nextData
;
}
break
;
default
:
KJ_FAIL_SYSCALL
(
"lseek(fd, pos, SEEK_HOLE)"
,
error
)
{
return
fromPos
-
fromOffset
;
}
}
// Write zeros.
off_t
zeroTo
=
kj
::
min
(
end
,
nextData
);
off_t
amount
=
zeroTo
-
fromPos
;
if
(
amount
>
0
)
{
zero
(
toPos
,
amount
);
toPos
+=
amount
;
fromPos
=
zeroTo
;
}
if
(
fromPos
==
end
)
{
return
fromPos
-
fromOffset
;
}
}
#endif
}
}
// Indicates caller should call File::copy() default implementation.
return
nullptr
;
}
// ReadableDirectory ---------------------------------------------------------
template
<
typename
Func
>
auto
list
(
bool
needTypes
,
Func
&&
func
)
->
Array
<
Decay
<
decltype
(
func
(
instance
<
StringPtr
>
(),
instance
<
FsNode
::
Type
>
()))
>>
{
// Seek to start of directory.
KJ_SYSCALL
(
lseek
(
fd
,
0
,
SEEK_SET
));
// Unfortunately, fdopendir() takes ownership of the file descriptor. Therefore we need to
// make a duplicate.
int
duped
;
KJ_SYSCALL
(
duped
=
dup
(
fd
));
DIR
*
dir
=
fdopendir
(
duped
);
if
(
dir
==
nullptr
)
{
close
(
duped
);
KJ_FAIL_SYSCALL
(
"fdopendir"
,
errno
);
}
KJ_DEFER
(
closedir
(
dir
));
kj
::
Vector
<
Decay
<
decltype
(
func
(
instance
<
StringPtr
>
(),
instance
<
FsNode
::
Type
>
()))
>>
entries
;
for
(;;)
{
errno
=
0
;
struct
dirent
*
entry
=
readdir
(
dir
);
if
(
entry
==
nullptr
)
{
int
error
=
errno
;
if
(
error
==
0
)
{
break
;
}
else
{
KJ_FAIL_SYSCALL
(
"readdir"
,
error
);
}
}
kj
::
StringPtr
name
=
entry
->
d_name
;
if
(
name
!=
"."
&&
name
!=
".."
&&
!
name
.
startsWith
(
HIDDEN_PREFIX
))
{
#ifdef DT_UNKNOWN // d_type is not available on all platforms.
if
(
entry
->
d_type
!=
DT_UNKNOWN
)
{
entries
.
add
(
func
(
name
,
modeToType
(
DTTOIF
(
entry
->
d_type
))));
}
else
{
#endif
if
(
needTypes
)
{
// Unknown type. Fall back to stat.
struct
stat
stats
;
KJ_SYSCALL
(
fstatat
(
fd
,
name
.
cStr
(),
&
stats
,
AT_SYMLINK_NOFOLLOW
));
entries
.
add
(
func
(
name
,
modeToType
(
stats
.
st_mode
)));
}
else
{
entries
.
add
(
func
(
name
,
FsNode
::
Type
::
OTHER
));
}
#ifdef DT_UNKNOWN
}
#endif
}
}
return
entries
.
releaseAsArray
();
}
Array
<
String
>
listNames
()
{
return
list
(
false
,
[](
StringPtr
name
,
FsNode
::
Type
type
)
{
return
heapString
(
name
);
});
}
Array
<
ReadableDirectory
::
Entry
>
listEntries
()
{
return
list
(
true
,
[](
StringPtr
name
,
FsNode
::
Type
type
)
{
return
ReadableDirectory
::
Entry
{
type
,
heapString
(
name
),
};
});
}
bool
exists
(
PathPtr
path
)
{
KJ_SYSCALL_HANDLE_ERRORS
(
faccessat
(
fd
,
path
.
toString
().
cStr
(),
F_OK
,
0
))
{
case
ENOENT
:
case
ENOTDIR
:
return
false
;
default
:
KJ_FAIL_SYSCALL
(
"faccessat(fd, path)"
,
error
,
path
)
{
return
false
;
}
}
return
true
;
}
Maybe
<
FsNode
::
Metadata
>
tryLstat
(
PathPtr
path
)
{
struct
stat
stats
;
KJ_SYSCALL_HANDLE_ERRORS
(
fstatat
(
fd
,
path
.
toString
().
cStr
(),
&
stats
,
AT_SYMLINK_NOFOLLOW
))
{
case
ENOENT
:
case
ENOTDIR
:
return
nullptr
;
default
:
KJ_FAIL_SYSCALL
(
"faccessat(fd, path)"
,
error
,
path
)
{
return
nullptr
;
}
}
return
statToMetadata
(
stats
);
}
Maybe
<
Own
<
ReadableFile
>>
tryOpenFile
(
PathPtr
path
)
{
int
newFd
;
KJ_SYSCALL_HANDLE_ERRORS
(
newFd
=
openat
(
fd
,
path
.
toString
().
cStr
(),
O_RDONLY
|
MAYBE_O_CLOEXEC
))
{
case
ENOENT
:
case
ENOTDIR
:
return
nullptr
;
default
:
KJ_FAIL_SYSCALL
(
"openat(fd, path, O_RDONLY)"
,
error
,
path
)
{
return
nullptr
;
}
}
kj
::
AutoCloseFd
result
(
newFd
);
#ifndef O_CLOEXEC
setCloexec
(
result
);
#endif
return
newDiskReadableFile
(
kj
::
mv
(
result
));
}
Maybe
<
AutoCloseFd
>
tryOpenSubdirInternal
(
PathPtr
path
)
{
int
newFd
;
KJ_SYSCALL_HANDLE_ERRORS
(
newFd
=
openat
(
fd
,
path
.
toString
().
cStr
(),
O_RDONLY
|
MAYBE_O_CLOEXEC
|
MAYBE_O_DIRECTORY
))
{
case
ENOENT
:
return
nullptr
;
case
ENOTDIR
:
// Could mean that a parent is not a directory, which we treat as "doesn't exist".
// Could also mean that the specified file is not a directory, which should throw.
// Check using exists().
if
(
!
exists
(
path
))
{
return
nullptr
;
}
// fallthrough
default
:
KJ_FAIL_SYSCALL
(
"openat(fd, path, O_DIRECTORY)"
,
error
,
path
)
{
return
nullptr
;
}
}
kj
::
AutoCloseFd
result
(
newFd
);
#ifndef O_CLOEXEC
setCloexec
(
result
);
#endif
return
kj
::
mv
(
result
);
}
Maybe
<
Own
<
ReadableDirectory
>>
tryOpenSubdir
(
PathPtr
path
)
{
return
tryOpenSubdirInternal
(
path
).
map
(
newDiskReadableDirectory
);
}
Maybe
<
String
>
tryReadlink
(
PathPtr
path
)
{
size_t
trySize
=
256
;
for
(;;)
{
KJ_STACK_ARRAY
(
char
,
buf
,
trySize
,
256
,
4096
);
ssize_t
n
=
readlinkat
(
fd
,
path
.
toString
().
cStr
(),
buf
.
begin
(),
buf
.
size
());
if
(
n
<
0
)
{
int
error
=
errno
;
switch
(
error
)
{
case
EINTR
:
continue
;
case
ENOENT
:
case
ENOTDIR
:
case
EINVAL
:
// not a link
return
nullptr
;
default
:
KJ_FAIL_SYSCALL
(
"readlinkat(fd, path)"
,
error
,
path
)
{
return
nullptr
;
}
}
}
if
(
n
>=
buf
.
size
())
{
// Didn't give it enough space. Better retry with a bigger buffer.
trySize
*=
2
;
continue
;
}
return
heapString
(
buf
.
begin
(),
n
);
}
}
// Directory -----------------------------------------------------------------
bool
tryMkdir
(
PathPtr
path
,
WriteMode
mode
,
bool
noThrow
)
{
// Internal function to make a directory.
auto
filename
=
path
.
toString
();
mode_t
acl
=
has
(
mode
,
WriteMode
::
PRIVATE
)
?
0700
:
0777
;
KJ_SYSCALL_HANDLE_ERRORS
(
mkdirat
(
fd
,
filename
.
cStr
(),
acl
))
{
case
EEXIST
:
{
// Apparently this path exists.
if
(
!
has
(
mode
,
WriteMode
::
MODIFY
))
{
// Require exclusive create.
return
false
;
}
// MODIFY is allowed, so we just need to check whether the existing entry is a directory.
struct
stat
stats
;
KJ_SYSCALL_HANDLE_ERRORS
(
fstatat
(
fd
,
filename
.
cStr
(),
&
stats
,
0
))
{
default
:
// mkdir() says EEXIST but we can't stat it. Maybe it's a dangling link, or maybe
// we can't access it for some reason. Assume failure.
//
// TODO(someday): Maybe we should be creating the directory at the target of the
// link?
goto
failed
;
}
return
(
stats
.
st_mode
&
S_IFMT
)
==
S_IFDIR
;
}
case
ENOENT
:
if
(
has
(
mode
,
WriteMode
::
CREATE_PARENT
)
&&
path
.
size
()
>
0
&&
tryMkdir
(
path
.
parent
(),
WriteMode
::
CREATE
|
WriteMode
::
MODIFY
|
WriteMode
::
CREATE_PARENT
,
true
))
{
// Retry, but make sure we don't try to create the parent again.
return
tryMkdir
(
path
,
mode
-
WriteMode
::
CREATE_PARENT
,
noThrow
);
}
else
{
goto
failed
;
}
default
:
failed
:
if
(
noThrow
)
{
// Caller requested no throwing.
return
false
;
}
else
{
KJ_FAIL_SYSCALL
(
"mkdirat(fd, path)"
,
error
,
path
);
}
}
return
true
;
}
kj
::
Maybe
<
String
>
createNamedTemporary
(
PathPtr
finalName
,
WriteMode
mode
,
Function
<
int
(
StringPtr
)
>
tryCreate
)
{
// Create a temporary file which will eventually replace `finalName`.
//
// Calls `tryCreate` to actually create the temporary, passing in the desired path. tryCreate()
// is expected to behave like a syscall, returning a negative value and setting `errno` on
// error. tryCreate() MUST fail with EEXIST if the path exists -- this is not checked in
// advance, since it needs to be checked atomically. In the case of EEXIST, tryCreate() will
// be called again with a new path.
//
// Returns the temporary path that succeeded. Only returns nullptr if there was an exception
// but we're complied with -fno-exceptions.
if
(
finalName
.
size
()
==
0
)
{
KJ_FAIL_REQUIRE
(
"can't replace self"
)
{
break
;
}
return
nullptr
;
}
static
uint
counter
=
0
;
static
const
pid_t
pid
=
getpid
();
String
pathPrefix
;
if
(
finalName
.
size
()
>
1
)
{
pathPrefix
=
kj
::
str
(
finalName
.
parent
(),
'/'
);
}
auto
path
=
kj
::
str
(
pathPrefix
,
HIDDEN_PREFIX
,
pid
,
'.'
,
counter
++
,
'.'
,
finalName
.
basename
()[
0
],
".partial"
);
KJ_SYSCALL_HANDLE_ERRORS
(
tryCreate
(
path
))
{
case
EEXIST
:
return
createNamedTemporary
(
finalName
,
mode
,
kj
::
mv
(
tryCreate
));
case
ENOENT
:
if
(
has
(
mode
,
WriteMode
::
CREATE_PARENT
)
&&
finalName
.
size
()
>
1
&&
tryMkdir
(
finalName
.
parent
(),
WriteMode
::
CREATE
|
WriteMode
::
MODIFY
|
WriteMode
::
CREATE_PARENT
,
true
))
{
// Retry, but make sure we don't try to create the parent again.
mode
=
mode
-
WriteMode
::
CREATE_PARENT
;
return
createNamedTemporary
(
finalName
,
mode
,
kj
::
mv
(
tryCreate
));
}
// fallthrough
default
:
KJ_FAIL_SYSCALL
(
"create(path)"
,
error
,
path
)
{
break
;
}
return
nullptr
;
}
return
kj
::
mv
(
path
);
}
bool
tryReplaceNode
(
PathPtr
path
,
WriteMode
mode
,
Function
<
int
(
StringPtr
)
>
tryCreate
)
{
// Replaces the given path with an object created by calling tryCreate().
//
// tryCreate() must behave like a syscall which creates the node at the path passed to it,
// returning a negative value on error. If the path passed to tryCreate already exists, it
// MUST fail with EEXIST.
//
// When `mode` includes MODIFY, replaceNode() reacts to EEXIST by creating the node in a
// temporary location and then rename()ing it into place.
if
(
path
.
size
()
==
0
)
{
KJ_FAIL_REQUIRE
(
"can't replace self"
)
{
return
false
;
}
}
auto
filename
=
path
.
toString
();
if
(
has
(
mode
,
WriteMode
::
CREATE
))
{
// First try just cerating the node in-place.
KJ_SYSCALL_HANDLE_ERRORS
(
tryCreate
(
filename
))
{
case
EEXIST
:
// Target exists.
if
(
has
(
mode
,
WriteMode
::
MODIFY
))
{
// Fall back to MODIFY path, below.
break
;
}
else
{
return
false
;
}
case
ENOENT
:
if
(
has
(
mode
,
WriteMode
::
CREATE_PARENT
)
&&
path
.
size
()
>
0
&&
tryMkdir
(
path
.
parent
(),
WriteMode
::
CREATE
|
WriteMode
::
MODIFY
|
WriteMode
::
CREATE_PARENT
,
true
))
{
// Retry, but make sure we don't try to create the parent again.
return
tryReplaceNode
(
path
,
mode
-
WriteMode
::
CREATE_PARENT
,
kj
::
mv
(
tryCreate
));
}
default
:
KJ_FAIL_SYSCALL
(
"create(path)"
,
error
,
path
)
{
return
false
;
}
}
else
{
// Success.
return
true
;
}
}
// Either we don't have CREATE mode or the target already exists. We need to perform a
// replacement instead.
KJ_IF_MAYBE
(
tempPath
,
createNamedTemporary
(
path
,
mode
,
kj
::
mv
(
tryCreate
)))
{
if
(
tryCommitReplacement
(
filename
,
fd
,
*
tempPath
,
mode
))
{
return
true
;
}
else
{
KJ_SYSCALL_HANDLE_ERRORS
(
unlinkat
(
fd
,
tempPath
->
cStr
(),
0
))
{
case
ENOENT
:
// meh
break
;
default
:
KJ_FAIL_SYSCALL
(
"unlinkat(fd, tempPath, 0)"
,
error
,
*
tempPath
);
}
return
false
;
}
}
else
{
// threw, but exceptions are disabled
return
false
;
}
}
Maybe
<
AutoCloseFd
>
tryOpenFileInternal
(
PathPtr
path
,
WriteMode
mode
,
bool
append
)
{
uint
flags
=
O_RDWR
|
MAYBE_O_CLOEXEC
;
mode_t
acl
=
0666
;
if
(
has
(
mode
,
WriteMode
::
CREATE
))
{
flags
|=
O_CREAT
;
}
if
(
!
has
(
mode
,
WriteMode
::
MODIFY
))
{
if
(
!
has
(
mode
,
WriteMode
::
CREATE
))
{
// Neither CREATE nor MODIFY -- impossible to satisfy preconditions.
return
nullptr
;
}
flags
|=
O_EXCL
;
}
if
(
append
)
{
flags
|=
O_APPEND
;
}
if
(
has
(
mode
,
WriteMode
::
EXECUTABLE
))
{
acl
=
0777
;
}
if
(
has
(
mode
,
WriteMode
::
PRIVATE
))
{
acl
&=
0700
;
}
auto
filename
=
path
.
toString
();
int
newFd
;
KJ_SYSCALL_HANDLE_ERRORS
(
newFd
=
openat
(
fd
,
filename
.
cStr
(),
flags
,
acl
))
{
case
ENOENT
:
if
(
has
(
mode
,
WriteMode
::
CREATE
))
{
// Either:
// - The file is a broken symlink.
// - A parent directory didn't exist.
if
(
has
(
mode
,
WriteMode
::
CREATE_PARENT
)
&&
path
.
size
()
>
0
&&
tryMkdir
(
path
.
parent
(),
WriteMode
::
CREATE
|
WriteMode
::
MODIFY
|
WriteMode
::
CREATE_PARENT
,
true
))
{
// Retry, but make sure we don't try to create the parent again.
return
tryOpenFileInternal
(
path
,
mode
-
WriteMode
::
CREATE_PARENT
,
append
);
}
// Check for broken link.
if
(
!
has
(
mode
,
WriteMode
::
MODIFY
)
&&
faccessat
(
fd
,
path
.
toString
().
cStr
(),
F_OK
,
AT_SYMLINK_NOFOLLOW
)
>=
0
)
{
// Yep. We treat this as already-exists, which means in CREATE-only mode this is a
// simple failure.
return
nullptr
;
}
KJ_FAIL_REQUIRE
(
"parent is not a directory"
,
path
)
{
return
nullptr
;
}
}
else
{
// MODIFY-only mode. ENOENT = doesn't exist = return null.
return
nullptr
;
}
case
ENOTDIR
:
if
(
!
has
(
mode
,
WriteMode
::
CREATE
))
{
// MODIFY-only mode. ENOTDIR = parent not a directory = doesn't exist = return null.
return
nullptr
;
}
goto
failed
;
case
EEXIST
:
if
(
!
has
(
mode
,
WriteMode
::
MODIFY
))
{
// CREATE-only mode. EEXIST = already exists = return null.
return
nullptr
;
}
goto
failed
;
default
:
failed
:
KJ_FAIL_SYSCALL
(
"openat(fd, path, O_RDWR | ...)"
,
error
,
path
)
{
return
nullptr
;
}
}
kj
::
AutoCloseFd
result
(
newFd
);
#ifndef O_CLOEXEC
setCloexec
(
result
);
#endif
return
kj
::
mv
(
result
);
}
bool
tryCommitReplacement
(
StringPtr
toPath
,
int
fromDirFd
,
StringPtr
fromPath
,
WriteMode
mode
,
int
*
errorReason
=
nullptr
)
{
if
(
has
(
mode
,
WriteMode
::
CREATE
)
&&
has
(
mode
,
WriteMode
::
MODIFY
))
{
// Always clobber. Try it.
KJ_SYSCALL_HANDLE_ERRORS
(
renameat
(
fromDirFd
,
fromPath
.
cStr
(),
fd
.
get
(),
toPath
.
cStr
()))
{
case
EISDIR
:
case
ENOTDIR
:
case
ENOTEMPTY
:
case
EEXIST
:
// Failed because target exists and due to the various weird quirks of rename(), it
// can't remove it for us. On Linux we can try an exchange instead. On others we have
// to move the target out of the way.
break
;
default
:
if
(
errorReason
==
nullptr
)
{
KJ_FAIL_SYSCALL
(
"rename(fromPath, toPath)"
,
error
,
fromPath
,
toPath
)
{
return
false
;
}
}
else
{
*
errorReason
=
error
;
return
false
;
}
}
else
{
return
true
;
}
}
#ifdef RENAME_EXCHANGE
// Try to use Linux's renameat2() to atomically check preconditions and apply.
if
(
has
(
mode
,
WriteMode
::
MODIFY
))
{
// Use an exchange to implement modification.
//
// We reach this branch when performing a MODIFY-only, or when performing a CREATE | MODIFY
// in which we determined above that there's a node of a different type blocking the
// exchange.
KJ_SYSCALL_HANDLE_ERRORS
(
syscall
(
SYS_renameat2
,
fromDirFd
,
fromPath
.
cStr
(),
fd
.
get
(),
toPath
.
cStr
(),
RENAME_EXCHANGE
))
{
case
ENOSYS
:
break
;
// fall back to traditional means
case
ENOENT
:
// Presumably because the target path doesn't exist.
if
(
has
(
mode
,
WriteMode
::
CREATE
))
{
KJ_FAIL_ASSERT
(
"rename(tmp, path) claimed path exists but "
"renameat2(fromPath, toPath, EXCAHNGE) said it doest; concurrent modification?"
,
fromPath
,
toPath
)
{
return
false
;
}
}
else
{
// Assume target doesn't exist.
return
false
;
}
default
:
if
(
errorReason
==
nullptr
)
{
KJ_FAIL_SYSCALL
(
"renameat2(fromPath, toPath, EXCHANGE)"
,
error
,
fromPath
,
toPath
)
{
return
false
;
}
}
else
{
*
errorReason
=
error
;
return
false
;
}
}
else
{
// Successful swap! Delete swapped-out content.
rmrf
(
fromDirFd
,
fromPath
);
return
true
;
}
}
else
if
(
has
(
mode
,
WriteMode
::
CREATE
))
{
KJ_SYSCALL_HANDLE_ERRORS
(
syscall
(
SYS_renameat2
,
fromDirFd
,
fromPath
.
cStr
(),
fd
.
get
(),
toPath
.
cStr
(),
RENAME_NOREPLACE
))
{
case
ENOSYS
:
break
;
// fall back to traditional means
case
EEXIST
:
return
false
;
default
:
if
(
errorReason
==
nullptr
)
{
KJ_FAIL_SYSCALL
(
"renameat2(fromPath, toPath, NOREPLACE)"
,
error
,
fromPath
,
toPath
)
{
return
false
;
}
}
else
{
*
errorReason
=
error
;
return
false
;
}
}
else
{
return
true
;
}
}
#endif
// We're unable to do what we wanted atomically. :(
if
(
has
(
mode
,
WriteMode
::
CREATE
)
&&
has
(
mode
,
WriteMode
::
MODIFY
))
{
// We failed to atomically delete the target previously. So now we need to do two calls in
// rapid succession to move the old file away then move the new one into place.
// Find out what kind of file exists at the target path.
struct
stat
stats
;
KJ_SYSCALL
(
fstatat
(
fd
,
toPath
.
cStr
(),
&
stats
,
AT_SYMLINK_NOFOLLOW
))
{
return
false
;
}
// Create a temporary location to move the existing object to. Note that rename() allows a
// non-directory to replace a non-directory, and allows a directory to replace an empty
// directory. So we have to create the right type.
Path
toPathParsed
=
Path
::
parse
(
toPath
);
String
away
;
KJ_IF_MAYBE
(
awayPath
,
createNamedTemporary
(
toPathParsed
,
WriteMode
::
CREATE
,
[
&
](
StringPtr
candidatePath
)
{
if
(
S_ISDIR
(
stats
.
st_mode
))
{
return
mkdirat
(
fd
,
candidatePath
.
cStr
(),
0700
);
}
else
{
return
mknodat
(
fd
,
candidatePath
.
cStr
(),
S_IFREG
|
0600
,
dev_t
());
}
}))
{
away
=
kj
::
mv
(
*
awayPath
);
}
else
{
// Already threw.
return
false
;
}
// OK, now move the target object to replace the thing we just created.
KJ_SYSCALL
(
renameat
(
fd
,
toPath
.
cStr
(),
fd
,
away
.
cStr
()))
{
// Something went wrong. Remove the thing we just created.
unlinkat
(
fd
,
away
.
cStr
(),
S_ISDIR
(
stats
.
st_mode
)
?
AT_REMOVEDIR
:
0
);
return
false
;
}
// Now move the source object to the target location.
KJ_SYSCALL_HANDLE_ERRORS
(
renameat
(
fromDirFd
,
fromPath
.
cStr
(),
fd
,
toPath
.
cStr
()))
{
default
:
// Try to put things back where they were. If this fails, though, then we have little
// choice but to leave things broken.
KJ_SYSCALL_HANDLE_ERRORS
(
renameat
(
fd
,
away
.
cStr
(),
fd
,
toPath
.
cStr
()))
{
default
:
break
;
}
if
(
errorReason
==
nullptr
)
{
KJ_FAIL_SYSCALL
(
"rename(fromPath, toPath)"
,
error
,
fromPath
,
toPath
)
{
return
false
;
}
}
else
{
*
errorReason
=
error
;
return
false
;
}
}
// OK, success. Delete the old content.
rmrf
(
fd
,
away
);
return
true
;
}
else
{
// Only one of CREATE or MODIFY is specified, so we need to verify non-atomically that the
// corresponding precondition (must-not-exist or must-exist, respectively) is held.
if
(
has
(
mode
,
WriteMode
::
CREATE
))
{
struct
stat
stats
;
KJ_SYSCALL_HANDLE_ERRORS
(
fstatat
(
fd
.
get
(),
toPath
.
cStr
(),
&
stats
,
AT_SYMLINK_NOFOLLOW
))
{
case
ENOENT
:
case
ENOTDIR
:
break
;
// doesn't exist; continue
default
:
KJ_FAIL_SYSCALL
(
"fstatat(fd, toPath)"
,
error
,
toPath
)
{
return
false
;
}
}
else
{
return
false
;
// already exists; fail
}
}
else
if
(
has
(
mode
,
WriteMode
::
MODIFY
))
{
struct
stat
stats
;
KJ_SYSCALL_HANDLE_ERRORS
(
fstatat
(
fd
.
get
(),
toPath
.
cStr
(),
&
stats
,
AT_SYMLINK_NOFOLLOW
))
{
case
ENOENT
:
case
ENOTDIR
:
return
false
;
// doesn't exist; fail
default
:
KJ_FAIL_SYSCALL
(
"fstatat(fd, toPath)"
,
error
,
toPath
)
{
return
false
;
}
}
else
{
// already exists; continue
}
}
else
{
// Neither CREATE nor MODIFY.
return
false
;
}
// Start over in create-and-modify mode.
return
tryCommitReplacement
(
toPath
,
fromDirFd
,
fromPath
,
WriteMode
::
CREATE
|
WriteMode
::
MODIFY
,
errorReason
);
}
}
template
<
typename
T
>
class
ReplacerImpl
:
public
Directory
::
Replacer
<
T
>
{
public
:
ReplacerImpl
(
Own
<
T
>&&
object
,
DiskHandle
&
handle
,
String
&&
tempPath
,
String
&&
path
,
WriteMode
mode
)
:
Directory
::
Replacer
<
T
>
(
mode
),
object
(
kj
::
mv
(
object
)),
handle
(
handle
),
tempPath
(
kj
::
mv
(
tempPath
)),
path
(
kj
::
mv
(
path
))
{}
~
ReplacerImpl
()
noexcept
(
false
)
{
if
(
!
committed
)
{
rmrf
(
handle
.
fd
,
tempPath
);
}
}
T
&
get
()
override
{
return
*
object
;
}
bool
tryCommit
()
override
{
KJ_ASSERT
(
!
committed
,
"already committed"
)
{
return
false
;
}
return
committed
=
handle
.
tryCommitReplacement
(
path
,
handle
.
fd
,
tempPath
,
Directory
::
Replacer
<
T
>::
mode
);
}
private
:
Own
<
T
>
object
;
DiskHandle
&
handle
;
String
tempPath
;
String
path
;
bool
committed
=
false
;
// true if *successfully* committed (in which case tempPath is gone)
};
template
<
typename
T
>
class
BrokenReplacer
:
public
Directory
::
Replacer
<
T
>
{
// For recovery path when exceptions are disabled.
public
:
BrokenReplacer
(
Own
<
T
>
inner
)
:
Directory
::
Replacer
<
T
>
(
WriteMode
::
CREATE
|
WriteMode
::
MODIFY
),
inner
(
kj
::
mv
(
inner
))
{}
T
&
get
()
override
{
return
*
inner
;
}
bool
tryCommit
()
override
{
return
false
;
}
private
:
Own
<
T
>
inner
;
};
Maybe
<
Own
<
File
>>
tryOpenFile
(
PathPtr
path
,
WriteMode
mode
)
{
return
tryOpenFileInternal
(
path
,
mode
,
false
).
map
(
newDiskFile
);
}
Own
<
Directory
::
Replacer
<
File
>>
replaceFile
(
PathPtr
path
,
WriteMode
mode
)
{
mode_t
acl
=
0666
;
if
(
has
(
mode
,
WriteMode
::
EXECUTABLE
))
{
acl
=
0777
;
}
if
(
has
(
mode
,
WriteMode
::
PRIVATE
))
{
acl
&=
0700
;
}
int
newFd_
;
KJ_IF_MAYBE
(
temp
,
createNamedTemporary
(
path
,
mode
,
[
&
](
StringPtr
candidatePath
)
{
return
newFd_
=
openat
(
fd
,
candidatePath
.
cStr
(),
O_RDWR
|
O_CREAT
|
O_EXCL
|
MAYBE_O_CLOEXEC
,
acl
);
}))
{
AutoCloseFd
newFd
(
newFd_
);
#ifndef O_CLOEXEC
setCloexec
(
newFd
);
#endif
return
heap
<
ReplacerImpl
<
File
>>
(
newDiskFile
(
kj
::
mv
(
newFd
)),
*
this
,
kj
::
mv
(
*
temp
),
path
.
toString
(),
mode
);
}
else
{
// threw, but exceptions are disabled
return
heap
<
BrokenReplacer
<
File
>>
(
newInMemoryFile
(
nullClock
()));
}
}
Own
<
File
>
createTemporary
()
{
int
newFd_
;
#ifdef O_TMPFILE
// Use syscall() to work around glibc bug with O_TMPFILE:
// https://sourceware.org/bugzilla/show_bug.cgi?id=17523
KJ_SYSCALL_HANDLE_ERRORS
(
newFd_
=
syscall
(
SYS_openat
,
fd
.
get
(),
"."
,
O_RDWR
|
O_TMPFILE
,
0700
))
{
case
EOPNOTSUPP
:
case
EINVAL
:
// Maybe not supported by this kernel / filesystem. Fall back to below.
break
;
default
:
KJ_FAIL_SYSCALL
(
"open(O_TMPFILE)"
,
error
)
{
break
;
}
break
;
}
else
{
AutoCloseFd
newFd
(
newFd_
);
#ifndef O_CLOEXEC
setCloexec
(
newFd
);
#endif
return
newDiskFile
(
kj
::
mv
(
newFd
));
}
#endif
KJ_IF_MAYBE
(
temp
,
createNamedTemporary
(
Path
(
"unnamed"
),
WriteMode
::
CREATE
,
[
&
](
StringPtr
path
)
{
return
newFd_
=
openat
(
fd
,
path
.
cStr
(),
O_RDWR
|
O_CREAT
|
O_EXCL
|
MAYBE_O_CLOEXEC
,
0600
);
}))
{
AutoCloseFd
newFd
(
newFd_
);
#ifndef O_CLOEXEC
setCloexec
(
newFd
);
#endif
auto
result
=
newDiskFile
(
kj
::
mv
(
newFd
));
KJ_SYSCALL
(
unlinkat
(
fd
,
temp
->
cStr
(),
0
))
{
break
;
}
return
kj
::
mv
(
result
);
}
else
{
// threw, but exceptions are disabled
return
newInMemoryFile
(
nullClock
());
}
}
Maybe
<
Own
<
AppendableFile
>>
tryAppendFile
(
PathPtr
path
,
WriteMode
mode
)
{
return
tryOpenFileInternal
(
path
,
mode
,
true
).
map
(
newDiskAppendableFile
);
}
Maybe
<
Own
<
Directory
>>
tryOpenSubdir
(
PathPtr
path
,
WriteMode
mode
)
{
// Must create before open.
if
(
has
(
mode
,
WriteMode
::
CREATE
))
{
if
(
!
tryMkdir
(
path
,
mode
,
false
))
return
nullptr
;
}
return
tryOpenSubdirInternal
(
path
).
map
(
newDiskDirectory
);
}
Own
<
Directory
::
Replacer
<
Directory
>>
replaceSubdir
(
PathPtr
path
,
WriteMode
mode
)
{
mode_t
acl
=
has
(
mode
,
WriteMode
::
PRIVATE
)
?
0700
:
0777
;
KJ_IF_MAYBE
(
temp
,
createNamedTemporary
(
path
,
mode
,
[
&
](
StringPtr
candidatePath
)
{
return
mkdirat
(
fd
,
candidatePath
.
cStr
(),
acl
);
}))
{
int
subdirFd_
;
KJ_SYSCALL_HANDLE_ERRORS
(
subdirFd_
=
openat
(
fd
,
temp
->
cStr
(),
O_RDONLY
|
MAYBE_O_CLOEXEC
|
MAYBE_O_DIRECTORY
))
{
default
:
KJ_FAIL_SYSCALL
(
"open(just-created-temporary)"
,
error
);
return
heap
<
BrokenReplacer
<
Directory
>>
(
newInMemoryDirectory
(
nullClock
()));
}
AutoCloseFd
subdirFd
(
subdirFd_
);
#ifndef O_CLOEXEC
setCloexec
(
subdirFd
);
#endif
return
heap
<
ReplacerImpl
<
Directory
>>
(
newDiskDirectory
(
kj
::
mv
(
subdirFd
)),
*
this
,
kj
::
mv
(
*
temp
),
path
.
toString
(),
mode
);
}
else
{
// threw, but exceptions are disabled
return
heap
<
BrokenReplacer
<
Directory
>>
(
newInMemoryDirectory
(
nullClock
()));
}
}
bool
trySymlink
(
PathPtr
linkpath
,
StringPtr
content
,
WriteMode
mode
)
{
return
tryReplaceNode
(
linkpath
,
mode
,
[
&
](
StringPtr
candidatePath
)
{
return
symlinkat
(
content
.
cStr
(),
fd
,
candidatePath
.
cStr
());
});
}
bool
tryTransfer
(
PathPtr
toPath
,
WriteMode
toMode
,
Directory
&
fromDirectory
,
PathPtr
fromPath
,
TransferMode
mode
,
Directory
&
self
)
{
KJ_REQUIRE
(
toPath
.
size
()
>
0
,
"can't replace self"
)
{
return
false
;
}
if
(
mode
==
TransferMode
::
LINK
)
{
KJ_IF_MAYBE
(
fromFd
,
fromDirectory
.
getFd
())
{
// Other is a disk directory, so we can hopefully do an efficient move/link.
return
tryReplaceNode
(
toPath
,
toMode
,
[
&
](
StringPtr
candidatePath
)
{
return
linkat
(
*
fromFd
,
fromPath
.
toString
().
cStr
(),
fd
,
candidatePath
.
cStr
(),
0
);
});
};
}
else
if
(
mode
==
TransferMode
::
MOVE
)
{
KJ_IF_MAYBE
(
fromFd
,
fromDirectory
.
getFd
())
{
KJ_ASSERT
(
mode
==
TransferMode
::
MOVE
);
int
error
=
0
;
if
(
tryCommitReplacement
(
toPath
.
toString
(),
*
fromFd
,
fromPath
.
toString
(),
toMode
,
&
error
))
{
return
true
;
}
else
switch
(
error
)
{
case
0
:
// Plain old WriteMode precondition failure.
return
false
;
case
EXDEV
:
// Can't move between devices. Fall back to default implementation, which does
// copy/delete.
break
;
case
ENOENT
:
// Either the destination directory doesn't exist or the source path doesn't exist.
// Unfortunately we don't really know. If CREATE_PARENT was provided, try creating
// the parent directory. Otherwise, we don't actually need to distinguish between
// these two errors; just return false.
if
(
has
(
toMode
,
WriteMode
::
CREATE
)
&&
has
(
toMode
,
WriteMode
::
CREATE_PARENT
)
&&
toPath
.
size
()
>
0
&&
tryMkdir
(
toPath
.
parent
(),
WriteMode
::
CREATE
|
WriteMode
::
MODIFY
|
WriteMode
::
CREATE_PARENT
,
true
))
{
// Retry, but make sure we don't try to create the parent again.
return
tryTransfer
(
toPath
,
toMode
-
WriteMode
::
CREATE_PARENT
,
fromDirectory
,
fromPath
,
mode
,
self
);
}
return
false
;
default
:
KJ_FAIL_SYSCALL
(
"rename(fromPath, toPath)"
,
error
,
fromPath
,
toPath
)
{
return
false
;
}
}
}
}
// OK, we can't do anything efficient using the OS. Fall back to default implementation.
return
self
.
Directory
::
tryTransfer
(
toPath
,
toMode
,
fromDirectory
,
fromPath
,
mode
);
}
bool
tryRemove
(
PathPtr
path
)
{
return
rmrf
(
fd
,
path
.
toString
());
}
protected
:
AutoCloseFd
fd
;
};
#define FSNODE_METHODS(classname) \
Maybe<int> getFd() override { return DiskHandle::getFd(); } \
\
Own<OsHandle> cloneOsHandle() override { \
return heap<classname>(DiskHandle::clone()); \
} \
\
Metadata stat() override { return DiskHandle::stat(); } \
void sync() override { DiskHandle::sync(); } \
void datasync() override { DiskHandle::datasync(); }
class
DiskReadableFile
final
:
public
ReadableFile
,
public
DiskHandle
{
public
:
DiskReadableFile
(
AutoCloseFd
&&
fd
)
:
DiskHandle
(
kj
::
mv
(
fd
))
{}
FSNODE_METHODS
(
DiskReadableFile
);
size_t
read
(
uint64_t
offset
,
ArrayPtr
<
byte
>
buffer
)
override
{
return
DiskHandle
::
read
(
offset
,
buffer
);
}
Array
<
const
byte
>
mmap
(
uint64_t
offset
,
uint64_t
size
)
override
{
return
DiskHandle
::
mmap
(
offset
,
size
);
}
Array
<
byte
>
mmapPrivate
(
uint64_t
offset
,
uint64_t
size
)
override
{
return
DiskHandle
::
mmapPrivate
(
offset
,
size
);
}
};
class
DiskAppendableFile
final
:
public
AppendableFile
,
public
DiskHandle
,
public
FdOutputStream
{
public
:
DiskAppendableFile
(
AutoCloseFd
&&
fd
)
:
DiskHandle
(
kj
::
mv
(
fd
)),
FdOutputStream
(
DiskHandle
::
fd
.
get
())
{}
FSNODE_METHODS
(
DiskAppendableFile
);
void
write
(
const
void
*
buffer
,
size_t
size
)
override
{
FdOutputStream
::
write
(
buffer
,
size
);
}
void
write
(
ArrayPtr
<
const
ArrayPtr
<
const
byte
>>
pieces
)
override
{
FdOutputStream
::
write
(
pieces
);
}
};
class
DiskFile
final
:
public
File
,
public
DiskHandle
{
public
:
DiskFile
(
AutoCloseFd
&&
fd
)
:
DiskHandle
(
kj
::
mv
(
fd
))
{}
FSNODE_METHODS
(
DiskFile
);
size_t
read
(
uint64_t
offset
,
ArrayPtr
<
byte
>
buffer
)
override
{
return
DiskHandle
::
read
(
offset
,
buffer
);
}
Array
<
const
byte
>
mmap
(
uint64_t
offset
,
uint64_t
size
)
override
{
return
DiskHandle
::
mmap
(
offset
,
size
);
}
Array
<
byte
>
mmapPrivate
(
uint64_t
offset
,
uint64_t
size
)
override
{
return
DiskHandle
::
mmapPrivate
(
offset
,
size
);
}
void
write
(
uint64_t
offset
,
ArrayPtr
<
const
byte
>
data
)
override
{
DiskHandle
::
write
(
offset
,
data
);
}
void
zero
(
uint64_t
offset
,
uint64_t
size
)
override
{
DiskHandle
::
zero
(
offset
,
size
);
}
void
truncate
(
uint64_t
size
)
override
{
DiskHandle
::
truncate
(
size
);
}
Own
<
WritableFileMapping
>
mmapWritable
(
uint64_t
offset
,
uint64_t
size
)
override
{
return
DiskHandle
::
mmapWritable
(
offset
,
size
);
}
size_t
copy
(
uint64_t
offset
,
ReadableFile
&
from
,
uint64_t
fromOffset
,
uint64_t
size
)
override
{
KJ_IF_MAYBE
(
result
,
DiskHandle
::
copy
(
offset
,
from
,
fromOffset
,
size
))
{
return
*
result
;
}
else
{
return
File
::
copy
(
offset
,
from
,
fromOffset
,
size
);
}
}
};
class
DiskReadableDirectory
final
:
public
ReadableDirectory
,
public
DiskHandle
{
public
:
DiskReadableDirectory
(
AutoCloseFd
&&
fd
)
:
DiskHandle
(
kj
::
mv
(
fd
))
{}
FSNODE_METHODS
(
DiskReadableDirectory
);
Array
<
String
>
listNames
()
override
{
return
DiskHandle
::
listNames
();
}
Array
<
Entry
>
listEntries
()
override
{
return
DiskHandle
::
listEntries
();
}
bool
exists
(
PathPtr
path
)
override
{
return
DiskHandle
::
exists
(
path
);
}
Maybe
<
FsNode
::
Metadata
>
tryLstat
(
PathPtr
path
)
override
{
return
DiskHandle
::
tryLstat
(
path
);
}
Maybe
<
Own
<
ReadableFile
>>
tryOpenFile
(
PathPtr
path
)
override
{
return
DiskHandle
::
tryOpenFile
(
path
);
}
Maybe
<
Own
<
ReadableDirectory
>>
tryOpenSubdir
(
PathPtr
path
)
override
{
return
DiskHandle
::
tryOpenSubdir
(
path
);
}
Maybe
<
String
>
tryReadlink
(
PathPtr
path
)
override
{
return
DiskHandle
::
tryReadlink
(
path
);
}
};
class
DiskDirectory
final
:
public
Directory
,
public
DiskHandle
{
public
:
DiskDirectory
(
AutoCloseFd
&&
fd
)
:
DiskHandle
(
kj
::
mv
(
fd
))
{}
FSNODE_METHODS
(
DiskDirectory
);
Array
<
String
>
listNames
()
override
{
return
DiskHandle
::
listNames
();
}
Array
<
Entry
>
listEntries
()
override
{
return
DiskHandle
::
listEntries
();
}
bool
exists
(
PathPtr
path
)
override
{
return
DiskHandle
::
exists
(
path
);
}
Maybe
<
FsNode
::
Metadata
>
tryLstat
(
PathPtr
path
)
override
{
return
DiskHandle
::
tryLstat
(
path
);
}
Maybe
<
Own
<
ReadableFile
>>
tryOpenFile
(
PathPtr
path
)
override
{
return
DiskHandle
::
tryOpenFile
(
path
);
}
Maybe
<
Own
<
ReadableDirectory
>>
tryOpenSubdir
(
PathPtr
path
)
override
{
return
DiskHandle
::
tryOpenSubdir
(
path
);
}
Maybe
<
String
>
tryReadlink
(
PathPtr
path
)
override
{
return
DiskHandle
::
tryReadlink
(
path
);
}
Maybe
<
Own
<
File
>>
tryOpenFile
(
PathPtr
path
,
WriteMode
mode
)
override
{
return
DiskHandle
::
tryOpenFile
(
path
,
mode
);
}
Own
<
Replacer
<
File
>>
replaceFile
(
PathPtr
path
,
WriteMode
mode
)
override
{
return
DiskHandle
::
replaceFile
(
path
,
mode
);
}
Own
<
File
>
createTemporary
()
override
{
return
DiskHandle
::
createTemporary
();
}
Maybe
<
Own
<
AppendableFile
>>
tryAppendFile
(
PathPtr
path
,
WriteMode
mode
)
override
{
return
DiskHandle
::
tryAppendFile
(
path
,
mode
);
}
Maybe
<
Own
<
Directory
>>
tryOpenSubdir
(
PathPtr
path
,
WriteMode
mode
)
override
{
return
DiskHandle
::
tryOpenSubdir
(
path
,
mode
);
}
Own
<
Replacer
<
Directory
>>
replaceSubdir
(
PathPtr
path
,
WriteMode
mode
)
override
{
return
DiskHandle
::
replaceSubdir
(
path
,
mode
);
}
bool
trySymlink
(
PathPtr
linkpath
,
StringPtr
content
,
WriteMode
mode
)
override
{
return
DiskHandle
::
trySymlink
(
linkpath
,
content
,
mode
);
}
bool
tryTransfer
(
PathPtr
toPath
,
WriteMode
toMode
,
Directory
&
fromDirectory
,
PathPtr
fromPath
,
TransferMode
mode
)
override
{
return
DiskHandle
::
tryTransfer
(
toPath
,
toMode
,
fromDirectory
,
fromPath
,
mode
,
*
this
);
}
// tryTransferTo() not implemented because we have nothing special we can do.
bool
tryRemove
(
PathPtr
path
)
override
{
return
DiskHandle
::
tryRemove
(
path
);
}
};
}
// namespace
Own
<
ReadableFile
>
newDiskReadableFile
(
kj
::
AutoCloseFd
fd
)
{
return
heap
<
DiskReadableFile
>
(
kj
::
mv
(
fd
));
}
Own
<
AppendableFile
>
newDiskAppendableFile
(
kj
::
AutoCloseFd
fd
)
{
return
heap
<
DiskAppendableFile
>
(
kj
::
mv
(
fd
));
}
Own
<
File
>
newDiskFile
(
kj
::
AutoCloseFd
fd
)
{
return
heap
<
DiskFile
>
(
kj
::
mv
(
fd
));
}
Own
<
ReadableDirectory
>
newDiskReadableDirectory
(
kj
::
AutoCloseFd
fd
)
{
return
heap
<
DiskReadableDirectory
>
(
kj
::
mv
(
fd
));
}
Own
<
Directory
>
newDiskDirectory
(
kj
::
AutoCloseFd
fd
)
{
return
heap
<
DiskDirectory
>
(
kj
::
mv
(
fd
));
}
}
// namespace kj
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment