Skip to content

Commit 8659ab1

Browse files
committed
NFS3 demo server
1 parent fb70a0f commit 8659ab1

10 files changed

+1175
-0
lines changed

Package.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ var targets: [PackageDescription.Target] = [
6464
.product(name: "NIOEmbedded", package: "swift-nio"),
6565
.product(name: "NIOHTTP1", package: "swift-nio"),
6666
]),
67+
.executableTarget(
68+
name: "NIOExtrasNFS3Demo",
69+
dependencies: [
70+
"NIONFS3",
71+
"NIOExtras",
72+
.product(name: "NIO", package: "swift-nio"),
73+
.product(name: "Logging", package: "swift-log"),
74+
]),
6775
.target(
6876
name: "NIOSOCKS",
6977
dependencies: [
@@ -135,6 +143,7 @@ let package = Package(
135143
dependencies: [
136144
.package(url: "https://github.com/apple/swift-nio.git", from: "2.42.0"),
137145
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
146+
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
138147
],
139148
targets: targets
140149
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Logging
2+
import NIOCore
3+
4+
final class CloseOnErrorHandler: ChannelInboundHandler {
5+
typealias InboundIn = Never
6+
7+
private let logger: Logger
8+
9+
init(logger: Logger) {
10+
self.logger = logger
11+
}
12+
13+
func errorCaught(context: ChannelHandlerContext, error: Error) {
14+
self.logger.warning("encountered error, closing NFS connection", metadata: ["error": "\(error)"])
15+
context.close(promise: nil)
16+
}
17+
}
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
import NIOCore
2+
import NIONFS3
3+
import Logging
4+
5+
final class DummyFS: NFS3FileSystemNoAuth {
6+
struct ChildEntry {
7+
var name: String
8+
var index: Int
9+
}
10+
11+
struct InodeEntry {
12+
var type: NFS3FileType
13+
var children: [ChildEntry]
14+
}
15+
16+
private var files: [InodeEntry] = []
17+
private var root: Int = 7
18+
private let fileContent: ByteBuffer = {
19+
var buffer = ByteBuffer(repeating: UInt8(ascii: "A"), count: 1 * 1024 * 1024)
20+
buffer.setInteger(UInt8(ascii: "H"), at: 0)
21+
buffer.setInteger(UInt8(ascii: "L"), at: buffer.writerIndex - 2)
22+
buffer.setInteger(UInt8(ascii: "L"), at: buffer.writerIndex - 3)
23+
buffer.setInteger(UInt8(ascii: "O"), at: buffer.writerIndex - 1)
24+
return buffer
25+
}()
26+
27+
init() {
28+
// 0 doesn't exist?
29+
self.files.append(.init(type: .regular, children: []))
30+
31+
let idDirFileA = self.files.count
32+
self.files.append(.init(type: .regular, children: []))
33+
34+
let idDirFileB = self.files.count
35+
self.files.append(.init(type: .regular, children: []))
36+
37+
let idDirFileC = self.files.count
38+
self.files.append(.init(type: .regular, children: []))
39+
40+
let idDirFileD = self.files.count
41+
self.files.append(.init(type: .regular, children: []))
42+
43+
let idDirFileE = self.files.count
44+
self.files.append(.init(type: .regular, children: []))
45+
46+
let idDirFileF = self.files.count
47+
self.files.append(.init(type: .regular, children: []))
48+
49+
let idDir = self.files.count
50+
self.files.append(
51+
.init(
52+
type: .directory,
53+
children: [
54+
.init(name: ".", index: idDir),
55+
.init(name: "file", index: idDirFileA),
56+
.init(name: "file1", index: idDirFileB),
57+
.init(name: "file2", index: idDirFileC),
58+
.init(name: "file3", index: idDirFileD),
59+
.init(name: "file4", index: idDirFileE),
60+
.init(name: "file5", index: idDirFileF),
61+
]))
62+
63+
let idRoot = self.files.count
64+
self.files.append(
65+
.init(
66+
type: .directory,
67+
children: [
68+
.init(name: ".", index: idRoot),
69+
.init(name: "dir", index: idDir),
70+
]))
71+
72+
self.files[idDir].children.append(.init(name: "..", index: idRoot))
73+
self.files[idRoot].children.append(.init(name: "..", index: idRoot))
74+
75+
self.root = idRoot
76+
}
77+
78+
func mount(_ call: MountCallMount, logger: Logger, promise: EventLoopPromise<MountReplyMount>) {
79+
promise.succeed(.init(result: .okay(.init(fileHandle: NFS3FileHandle(UInt64(self.root))))))
80+
}
81+
82+
func unmount(_ call: MountCallUnmount, logger: Logger, promise: EventLoopPromise<MountReplyUnmount>) {
83+
promise.succeed(.init())
84+
}
85+
86+
func getattr(_ call: NFS3CallGetAttr, logger: Logger, promise: EventLoopPromise<NFS3ReplyGetAttr>) {
87+
if let result = self.getFile(call.fileHandle) {
88+
promise.succeed(.init(result: .okay(.init(attributes: result))))
89+
} else {
90+
promise.succeed(.init(result: .fail(.errorBADHANDLE, NFS3Nothing())))
91+
}
92+
}
93+
94+
func lookup(fileName: String, inDirectory dirHandle: NFS3FileHandle) -> (NFS3FileHandle, NFS3FileAttr)? {
95+
guard let dirEntry = self.getEntry(fileHandle: dirHandle) else {
96+
return nil
97+
}
98+
99+
guard let index = self.files[dirEntry.0].children.first(where: { $0.name == fileName })?.index else {
100+
return nil
101+
}
102+
let fileHandle = NFS3FileHandle(UInt64(index))
103+
104+
return (fileHandle, self.getFile(fileHandle)!)
105+
}
106+
107+
func getEntry(index: Int) -> InodeEntry? {
108+
guard index >= 0 && index < self.files.count else {
109+
return nil
110+
}
111+
return self.files[index]
112+
}
113+
114+
func getEntry(fileHandle: NFS3FileHandle) -> (Int, InodeEntry)? {
115+
return UInt64(fileHandle).flatMap {
116+
Int(exactly: $0)
117+
}.flatMap { index in
118+
self.getEntry(index: index).map {
119+
(index, $0)
120+
}
121+
}
122+
}
123+
124+
func getFile(_ fileHandle: NFS3FileHandle) -> NFS3FileAttr? {
125+
guard let entry = self.getEntry(fileHandle: fileHandle) else {
126+
return nil
127+
}
128+
129+
return .init(
130+
type: entry.1.type,
131+
mode: 0o777,
132+
nlink: 1,
133+
uid: 1,
134+
gid: 1,
135+
size: NFS3Size(rawValue: 1 * 1024 * 1024),
136+
used: 1,
137+
rdev: 1,
138+
fsid: 1,
139+
fileid: NFS3FileID(rawValue: UInt64(entry.0)),
140+
atime: .init(seconds: 0, nanoseconds: 0),
141+
mtime: .init(seconds: 0, nanoseconds: 0),
142+
ctime: .init(seconds: 0, nanoseconds: 0))
143+
}
144+
145+
func fsinfo(_ call: NFS3CallFSInfo, logger: Logger, promise: EventLoopPromise<NFS3ReplyFSInfo>) {
146+
promise.succeed(
147+
NFS3ReplyFSInfo(
148+
result: .okay(
149+
.init(
150+
attributes: nil,
151+
rtmax: 1_000_000,
152+
rtpref: 128_000,
153+
rtmult: 4096,
154+
wtmax: 1_000_000,
155+
wtpref: 128_000,
156+
wtmult: 4096,
157+
dtpref: 128_000,
158+
maxFileSize: NFS3Size(rawValue: UInt64(Int.max)),
159+
timeDelta: NFS3Time(seconds: 0, nanoseconds: 0),
160+
properties: .default))))
161+
}
162+
163+
func pathconf(_ call: NFS3CallPathConf, logger: Logger, promise: EventLoopPromise<NFS3ReplyPathConf>) {
164+
promise.succeed(
165+
.init(
166+
result: .okay(
167+
.init(
168+
attributes: nil,
169+
linkMax: 1_000_000,
170+
nameMax: 4096,
171+
noTrunc: false,
172+
chownRestricted: false,
173+
caseInsensitive: false,
174+
casePreserving: true))))
175+
}
176+
177+
func fsstat(_ call: NFS3CallFSStat, logger: Logger, promise: EventLoopPromise<NFS3ReplyFSStat>) {
178+
promise.succeed(
179+
.init(
180+
result: .okay(
181+
.init(
182+
attributes: nil,
183+
tbytes: 0x100_0000_0000,
184+
fbytes: 0,
185+
abytes: 0,
186+
tfiles: 0x1000_0000,
187+
ffiles: 0,
188+
afiles: 0,
189+
invarsec: 0))))
190+
}
191+
192+
func access(_ call: NFS3CallAccess, logger: Logger, promise: EventLoopPromise<NFS3ReplyAccess>) {
193+
promise.succeed(.init(result: .okay(.init(dirAttributes: nil, access: .allReadOnly))))
194+
}
195+
196+
func lookup(_ call: NFS3CallLookup, logger: Logger, promise: EventLoopPromise<NFS3ReplyLookup>) {
197+
if let entry = self.lookup(fileName: call.name, inDirectory: call.dir) {
198+
promise.succeed(
199+
.init(
200+
result: .okay(
201+
.init(
202+
fileHandle: entry.0,
203+
attributes: entry.1,
204+
dirAttributes: nil))))
205+
} else {
206+
promise.succeed(.init(result: .fail(.errorNOENT, .init(dirAttributes: nil))))
207+
208+
}
209+
}
210+
211+
func readdirplus(_ call: NFS3CallReadDirPlus, logger: Logger, promise: EventLoopPromise<NFS3ReplyReadDirPlus>) {
212+
if let entry = self.getEntry(fileHandle: call.fileHandle) {
213+
var entries: [NFS3ReplyReadDirPlus.Entry] = []
214+
for fileIndex in entry.1.children.enumerated().dropFirst(Int(min(UInt64(Int.max),
215+
call.cookie.rawValue))) {
216+
entries.append(
217+
.init(
218+
fileID: NFS3FileID(rawValue: UInt64(fileIndex.element.index)),
219+
fileName: fileIndex.element.name,
220+
cookie: NFS3Cookie(rawValue: UInt64(fileIndex.offset)),
221+
nameAttributes: nil,
222+
nameHandle: nil))
223+
}
224+
promise.succeed(
225+
.init(
226+
result: .okay(
227+
.init(
228+
dirAttributes: nil,
229+
cookieVerifier: call.cookieVerifier,
230+
entries: entries,
231+
eof: true))))
232+
} else {
233+
promise.succeed(.init(result: .fail(.errorNOENT, .init(dirAttributes: nil))))
234+
235+
}
236+
}
237+
238+
func read(_ call: NFS3CallRead, logger: Logger, promise: EventLoopPromise<NFS3ReplyRead>) {
239+
if let file = self.getFile(call.fileHandle) {
240+
if file.type == .regular {
241+
var slice = self.fileContent
242+
guard call.offset.rawValue <= UInt64(Int.max) else {
243+
promise.succeed(.init(result: .fail(.errorFBIG, .init(attributes: nil))))
244+
return
245+
}
246+
let offsetLegal = slice.readSlice(length: Int(call.offset.rawValue)) != nil
247+
if offsetLegal {
248+
let actualSlice = slice.readSlice(length: min(slice.readableBytes, Int(call.count.rawValue)))!
249+
let isEOF = slice.readableBytes == 0
250+
251+
promise.succeed(
252+
.init(
253+
result: .okay(
254+
.init(
255+
attributes: nil,
256+
count: NFS3Count(rawValue: UInt32(actualSlice.readableBytes)),
257+
eof: isEOF,
258+
data: actualSlice))))
259+
} else {
260+
promise.succeed(
261+
.init(
262+
result: .okay(
263+
.init(
264+
attributes: nil,
265+
count: 0,
266+
eof: true,
267+
data: ByteBuffer()))))
268+
}
269+
} else {
270+
promise.succeed(.init(result: .fail(.errorISDIR, .init(attributes: nil))))
271+
}
272+
} else {
273+
promise.succeed(.init(result: .fail(.errorNOENT, .init(attributes: nil))))
274+
}
275+
}
276+
277+
func readlink(_ call: NFS3CallReadlink, logger: Logger, promise: EventLoopPromise<NFS3ReplyReadlink>) {
278+
promise.succeed(.init(result: .fail(.errorNOENT, .init(symlinkAttributes: nil))))
279+
}
280+
281+
func setattr(_ call: NFS3CallSetattr, logger: Logger, promise: EventLoopPromise<NFS3ReplySetattr>) {
282+
promise.succeed(.init(result: .fail(.errorROFS, .init(wcc: .init(before: nil, after: nil)))))
283+
}
284+
285+
func shutdown(promise: EventLoopPromise<Void>) {
286+
promise.succeed(())
287+
}
288+
}

0 commit comments

Comments
 (0)