1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
|
import SystemPackage
struct DirEntry {
let preopenPath: String?
let fd: FileDescriptor
}
extension DirEntry: WASIDir, FdWASIEntry {
func openFile(
symlinkFollow: Bool,
path: String,
oflags: WASIAbi.Oflags,
accessMode: FileAccessMode,
fdflags: WASIAbi.Fdflags
) throws -> FileDescriptor {
var options: FileDescriptor.OpenOptions = []
if !symlinkFollow {
options.insert(.noFollow)
}
if oflags.contains(.DIRECTORY) {
options.insert(.directory)
} else {
// For regular file
if oflags.contains(.CREAT) {
options.insert(.create)
}
if oflags.contains(.EXCL) {
options.insert(.exclusiveCreate)
}
if oflags.contains(.TRUNC) {
options.insert(.truncate)
}
}
// SystemPackage.FilePath implicitly normalizes the trailing "/", however
// it means the last component is expected to be a directory. Therefore
// check it here before converting path string to FilePath.
if path.hasSuffix("/") {
options.insert(.directory)
}
if fdflags.contains(.APPEND) {
options.insert(.append)
}
let mode: FileDescriptor.AccessMode
switch (accessMode.contains(.read), accessMode.contains(.write)) {
case (true, true): mode = .readWrite
case (true, false): mode = .readOnly
case (false, true): mode = .writeOnly
case (false, false):
// If not opened for neither write nor read, set read mode by default
// because underlying `openat` requires mode but WASI's
// `path_open` can omit FD_READ.
// https://man7.org/linux/man-pages/man2/open.2.html
// > The argument flags must include one of the following access
// > modes: O_RDONLY, O_WRONLY, or O_RDWR. These request opening the
// > file read-only, write-only, or read/write, respectively.
mode = .readOnly
}
let newFd = try SandboxPrimitives.openAt(
start: self.fd,
path: FilePath(path), mode: mode, options: options,
// Use 0o600 open mode as the minimum permission
permissions: .ownerReadWrite
)
return newFd
}
func setFilestatTimes(
path: String,
atim: WASIAbi.Timestamp, mtim: WASIAbi.Timestamp,
fstFlags: WASIAbi.FstFlags, symlinkFollow: Bool
) throws {
let fd = try openFile(
symlinkFollow: symlinkFollow, path: path,
oflags: [], accessMode: .write, fdflags: []
)
let (access, modification) = try WASIAbi.Timestamp.platformTimeSpec(
atim: atim, mtim: mtim, fstFlags: fstFlags
)
try WASIAbi.Errno.translatingPlatformErrno {
try fd.setTimes(access: access, modification: modification)
}
}
func removeFile(atPath path: String) throws {
let (dir, basename) = try SandboxPrimitives.openParent(start: fd, path: path)
try WASIAbi.Errno.translatingPlatformErrno {
try dir.remove(at: FilePath(basename), options: [])
}
}
func removeDirectory(atPath path: String) throws {
let (dir, basename) = try SandboxPrimitives.openParent(start: fd, path: path)
try WASIAbi.Errno.translatingPlatformErrno {
try dir.remove(at: FilePath(basename), options: .removeDirectory)
}
}
func symlink(from sourcePath: String, to destPath: String) throws {
let (destDir, destBasename) = try SandboxPrimitives.openParent(
start: fd, path: destPath
)
try WASIAbi.Errno.translatingPlatformErrno {
try destDir.createSymlink(original: FilePath(sourcePath), link: FilePath(destBasename))
}
}
func readEntries(
cookie: WASIAbi.DirCookie
) throws -> AnyIterator<Result<ReaddirElement, any Error>> {
// Duplicate fd because readdir takes the ownership of
// the given fd and closedir also close the underlying fd
let newFd = try WASIAbi.Errno.translatingPlatformErrno {
try fd.open(at: ".", .readOnly, options: [])
}
let iterator = try WASIAbi.Errno.translatingPlatformErrno {
try newFd.contentsOfDirectory()
}
.lazy.enumerated()
.map { (entryIndex, entry) in
return Result(catching: { () -> ReaddirElement in
let entry = try entry.get()
let name = entry.name
let stat = try WASIAbi.Errno.translatingPlatformErrno {
try fd.attributes(at: name, options: [])
}
let dirent = WASIAbi.Dirent(
// We can't use telldir and seekdir because the location data
// is valid for only the same dirp but and there is no way to
// share dirp among fd_readdir calls.
dNext: WASIAbi.DirCookie(entryIndex + 1),
dIno: stat.inode,
dirNameLen: WASIAbi.DirNameLen(name.utf8.count),
dType: WASIAbi.FileType(platformFileType: entry.fileType)
)
return (dirent, name)
})
}
.dropFirst(Int(cookie))
.makeIterator()
return AnyIterator(iterator)
}
func createDirectory(atPath path: String) throws {
let (dir, basename) = try SandboxPrimitives.openParent(start: fd, path: path)
try WASIAbi.Errno.translatingPlatformErrno {
try dir.createDirectory(at: FilePath(basename), permissions: .ownerReadWriteExecute)
}
}
func attributes(path: String, symlinkFollow: Bool) throws -> WASIAbi.Filestat {
var options: FileDescriptor.AtOptions = []
if !symlinkFollow {
options.insert(.noFollow)
}
let (dir, basename) = try SandboxPrimitives.openParent(start: fd, path: path)
let attributes = try basename.withCString { cBasename in
try WASIAbi.Errno.translatingPlatformErrno {
try dir.attributes(at: cBasename, options: options)
}
}
return WASIAbi.Filestat(stat: attributes)
}
}
|