Skip to content

Commit 1536750

Browse files
weissiJohannes Weiss
authored and
Johannes Weiss
committed
NFS3 demo server
1 parent efd416e commit 1536750

10 files changed

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

0 commit comments

Comments
 (0)