Skip to content

Commit

Permalink
NFS3 demo server
Browse files Browse the repository at this point in the history
  • Loading branch information
weissi committed Aug 16, 2023
1 parent fb70a0f commit 8659ab1
Show file tree
Hide file tree
Showing 10 changed files with 1,175 additions and 0 deletions.
9 changes: 9 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ var targets: [PackageDescription.Target] = [
.product(name: "NIOEmbedded", package: "swift-nio"),
.product(name: "NIOHTTP1", package: "swift-nio"),
]),
.executableTarget(
name: "NIOExtrasNFS3Demo",
dependencies: [
"NIONFS3",
"NIOExtras",
.product(name: "NIO", package: "swift-nio"),
.product(name: "Logging", package: "swift-log"),
]),
.target(
name: "NIOSOCKS",
dependencies: [
Expand Down Expand Up @@ -135,6 +143,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", from: "2.42.0"),
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
],
targets: targets
)
17 changes: 17 additions & 0 deletions Sources/NIOExtrasNFS3Demo/CloseOnErrorHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Logging
import NIOCore

final class CloseOnErrorHandler: ChannelInboundHandler {
typealias InboundIn = Never

private let logger: Logger

init(logger: Logger) {
self.logger = logger
}

func errorCaught(context: ChannelHandlerContext, error: Error) {
self.logger.warning("encountered error, closing NFS connection", metadata: ["error": "\(error)"])
context.close(promise: nil)
}
}
288 changes: 288 additions & 0 deletions Sources/NIOExtrasNFS3Demo/DummyFS.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
import NIOCore
import NIONFS3
import Logging

final class DummyFS: NFS3FileSystemNoAuth {
struct ChildEntry {
var name: String
var index: Int
}

struct InodeEntry {
var type: NFS3FileType
var children: [ChildEntry]
}

private var files: [InodeEntry] = []
private var root: Int = 7
private let fileContent: ByteBuffer = {
var buffer = ByteBuffer(repeating: UInt8(ascii: "A"), count: 1 * 1024 * 1024)
buffer.setInteger(UInt8(ascii: "H"), at: 0)
buffer.setInteger(UInt8(ascii: "L"), at: buffer.writerIndex - 2)
buffer.setInteger(UInt8(ascii: "L"), at: buffer.writerIndex - 3)
buffer.setInteger(UInt8(ascii: "O"), at: buffer.writerIndex - 1)
return buffer
}()

init() {
// 0 doesn't exist?
self.files.append(.init(type: .regular, children: []))

let idDirFileA = self.files.count
self.files.append(.init(type: .regular, children: []))

let idDirFileB = self.files.count
self.files.append(.init(type: .regular, children: []))

let idDirFileC = self.files.count
self.files.append(.init(type: .regular, children: []))

let idDirFileD = self.files.count
self.files.append(.init(type: .regular, children: []))

let idDirFileE = self.files.count
self.files.append(.init(type: .regular, children: []))

let idDirFileF = self.files.count
self.files.append(.init(type: .regular, children: []))

let idDir = self.files.count
self.files.append(
.init(
type: .directory,
children: [
.init(name: ".", index: idDir),
.init(name: "file", index: idDirFileA),
.init(name: "file1", index: idDirFileB),
.init(name: "file2", index: idDirFileC),
.init(name: "file3", index: idDirFileD),
.init(name: "file4", index: idDirFileE),
.init(name: "file5", index: idDirFileF),
]))

let idRoot = self.files.count
self.files.append(
.init(
type: .directory,
children: [
.init(name: ".", index: idRoot),
.init(name: "dir", index: idDir),
]))

self.files[idDir].children.append(.init(name: "..", index: idRoot))
self.files[idRoot].children.append(.init(name: "..", index: idRoot))

self.root = idRoot
}

func mount(_ call: MountCallMount, logger: Logger, promise: EventLoopPromise<MountReplyMount>) {
promise.succeed(.init(result: .okay(.init(fileHandle: NFS3FileHandle(UInt64(self.root))))))
}

func unmount(_ call: MountCallUnmount, logger: Logger, promise: EventLoopPromise<MountReplyUnmount>) {
promise.succeed(.init())
}

func getattr(_ call: NFS3CallGetAttr, logger: Logger, promise: EventLoopPromise<NFS3ReplyGetAttr>) {
if let result = self.getFile(call.fileHandle) {
promise.succeed(.init(result: .okay(.init(attributes: result))))
} else {
promise.succeed(.init(result: .fail(.errorBADHANDLE, NFS3Nothing())))
}
}

func lookup(fileName: String, inDirectory dirHandle: NFS3FileHandle) -> (NFS3FileHandle, NFS3FileAttr)? {
guard let dirEntry = self.getEntry(fileHandle: dirHandle) else {
return nil
}

guard let index = self.files[dirEntry.0].children.first(where: { $0.name == fileName })?.index else {
return nil
}
let fileHandle = NFS3FileHandle(UInt64(index))

return (fileHandle, self.getFile(fileHandle)!)
}

func getEntry(index: Int) -> InodeEntry? {
guard index >= 0 && index < self.files.count else {
return nil
}
return self.files[index]
}

func getEntry(fileHandle: NFS3FileHandle) -> (Int, InodeEntry)? {
return UInt64(fileHandle).flatMap {
Int(exactly: $0)
}.flatMap { index in
self.getEntry(index: index).map {
(index, $0)
}
}
}

func getFile(_ fileHandle: NFS3FileHandle) -> NFS3FileAttr? {
guard let entry = self.getEntry(fileHandle: fileHandle) else {
return nil
}

return .init(
type: entry.1.type,
mode: 0o777,
nlink: 1,
uid: 1,
gid: 1,
size: NFS3Size(rawValue: 1 * 1024 * 1024),
used: 1,
rdev: 1,
fsid: 1,
fileid: NFS3FileID(rawValue: UInt64(entry.0)),
atime: .init(seconds: 0, nanoseconds: 0),
mtime: .init(seconds: 0, nanoseconds: 0),
ctime: .init(seconds: 0, nanoseconds: 0))
}

func fsinfo(_ call: NFS3CallFSInfo, logger: Logger, promise: EventLoopPromise<NFS3ReplyFSInfo>) {
promise.succeed(
NFS3ReplyFSInfo(
result: .okay(
.init(
attributes: nil,
rtmax: 1_000_000,
rtpref: 128_000,
rtmult: 4096,
wtmax: 1_000_000,
wtpref: 128_000,
wtmult: 4096,
dtpref: 128_000,
maxFileSize: NFS3Size(rawValue: UInt64(Int.max)),
timeDelta: NFS3Time(seconds: 0, nanoseconds: 0),
properties: .default))))
}

func pathconf(_ call: NFS3CallPathConf, logger: Logger, promise: EventLoopPromise<NFS3ReplyPathConf>) {
promise.succeed(
.init(
result: .okay(
.init(
attributes: nil,
linkMax: 1_000_000,
nameMax: 4096,
noTrunc: false,
chownRestricted: false,
caseInsensitive: false,
casePreserving: true))))
}

func fsstat(_ call: NFS3CallFSStat, logger: Logger, promise: EventLoopPromise<NFS3ReplyFSStat>) {
promise.succeed(
.init(
result: .okay(
.init(
attributes: nil,
tbytes: 0x100_0000_0000,
fbytes: 0,
abytes: 0,
tfiles: 0x1000_0000,
ffiles: 0,
afiles: 0,
invarsec: 0))))
}

func access(_ call: NFS3CallAccess, logger: Logger, promise: EventLoopPromise<NFS3ReplyAccess>) {
promise.succeed(.init(result: .okay(.init(dirAttributes: nil, access: .allReadOnly))))
}

func lookup(_ call: NFS3CallLookup, logger: Logger, promise: EventLoopPromise<NFS3ReplyLookup>) {
if let entry = self.lookup(fileName: call.name, inDirectory: call.dir) {
promise.succeed(
.init(
result: .okay(
.init(
fileHandle: entry.0,
attributes: entry.1,
dirAttributes: nil))))
} else {
promise.succeed(.init(result: .fail(.errorNOENT, .init(dirAttributes: nil))))

}
}

func readdirplus(_ call: NFS3CallReadDirPlus, logger: Logger, promise: EventLoopPromise<NFS3ReplyReadDirPlus>) {
if let entry = self.getEntry(fileHandle: call.fileHandle) {
var entries: [NFS3ReplyReadDirPlus.Entry] = []
for fileIndex in entry.1.children.enumerated().dropFirst(Int(min(UInt64(Int.max),
call.cookie.rawValue))) {
entries.append(
.init(
fileID: NFS3FileID(rawValue: UInt64(fileIndex.element.index)),
fileName: fileIndex.element.name,
cookie: NFS3Cookie(rawValue: UInt64(fileIndex.offset)),
nameAttributes: nil,
nameHandle: nil))
}
promise.succeed(
.init(
result: .okay(
.init(
dirAttributes: nil,
cookieVerifier: call.cookieVerifier,
entries: entries,
eof: true))))
} else {
promise.succeed(.init(result: .fail(.errorNOENT, .init(dirAttributes: nil))))

}
}

func read(_ call: NFS3CallRead, logger: Logger, promise: EventLoopPromise<NFS3ReplyRead>) {
if let file = self.getFile(call.fileHandle) {
if file.type == .regular {
var slice = self.fileContent
guard call.offset.rawValue <= UInt64(Int.max) else {
promise.succeed(.init(result: .fail(.errorFBIG, .init(attributes: nil))))
return
}
let offsetLegal = slice.readSlice(length: Int(call.offset.rawValue)) != nil
if offsetLegal {
let actualSlice = slice.readSlice(length: min(slice.readableBytes, Int(call.count.rawValue)))!
let isEOF = slice.readableBytes == 0

promise.succeed(
.init(
result: .okay(
.init(
attributes: nil,
count: NFS3Count(rawValue: UInt32(actualSlice.readableBytes)),
eof: isEOF,
data: actualSlice))))
} else {
promise.succeed(
.init(
result: .okay(
.init(
attributes: nil,
count: 0,
eof: true,
data: ByteBuffer()))))
}
} else {
promise.succeed(.init(result: .fail(.errorISDIR, .init(attributes: nil))))
}
} else {
promise.succeed(.init(result: .fail(.errorNOENT, .init(attributes: nil))))
}
}

func readlink(_ call: NFS3CallReadlink, logger: Logger, promise: EventLoopPromise<NFS3ReplyReadlink>) {
promise.succeed(.init(result: .fail(.errorNOENT, .init(symlinkAttributes: nil))))
}

func setattr(_ call: NFS3CallSetattr, logger: Logger, promise: EventLoopPromise<NFS3ReplySetattr>) {
promise.succeed(.init(result: .fail(.errorROFS, .init(wcc: .init(before: nil, after: nil)))))
}

func shutdown(promise: EventLoopPromise<Void>) {
promise.succeed(())
}
}
Loading

0 comments on commit 8659ab1

Please sign in to comment.