1use crate::llm::actions::{
16 protocol_trait::{ActionResult, Protocol, Server},
17 ActionDefinition, Parameter,
18};
19use crate::protocol::log_template::LogTemplate;
20use crate::protocol::EventType;
21use crate::state::app_state::AppState;
22use anyhow::{Context, Result};
23use serde_json::json;
24use std::sync::LazyLock;
25
26pub struct NfsProtocol;
28
29impl NfsProtocol {
30 pub fn new() -> Self {
31 Self
32 }
33}
34
35impl Protocol for NfsProtocol {
36 fn get_async_actions(&self, _state: &AppState) -> Vec<ActionDefinition> {
43 Vec::new()
44 }
45
46 fn get_sync_actions(&self) -> Vec<ActionDefinition> {
47 nfs_response_actions()
48 }
49
50 fn protocol_name(&self) -> &'static str {
51 "NFS"
52 }
53
54 fn get_event_types(&self) -> Vec<EventType> {
55 get_nfs_event_types()
56 }
57
58 fn stack_name(&self) -> &'static str {
59 "ETH>IP>TCP>NFS"
60 }
61
62 fn keywords(&self) -> Vec<&'static str> {
63 vec!["nfs", "file server"]
64 }
65
66 fn metadata(&self) -> crate::protocol::metadata::ProtocolMetadataV2 {
67 use crate::protocol::metadata::{DevelopmentState, ProtocolMetadataV2};
68
69 ProtocolMetadataV2::builder()
70 .state(DevelopmentState::Experimental)
71 .well_known_port(2049)
72 .implementation(
76 "nfsserve v0.10 NFSv3 server; LlmNfsFileSystem implements NFSFileSystem",
77 )
78 .llm_control(
79 "Every filesystem operation: lookup, getattr, setattr, read, write, create, \
80 mkdir, remove, rename, readdir, symlink, readlink. Nothing is stored server-side.",
81 )
82 .e2e_testing("mount / showmount, and tests/server/nfs/test.rs")
83 .notes(
84 "NFSv3 over TCP only. File contents are UTF-8 strings in actions, so binary \
85 files cannot be served or received faithfully. The LLM must keep file IDs \
86 stable across calls or clients will misbehave. Every operation costs one \
87 model round-trip, so real workloads are impractically slow - use script or \
88 static handlers for anything beyond a demo.",
89 )
90 .max_inbound_bytes(crate::server::nfs::guard::MAX_RECORD_BYTES)
91 .build()
92 }
93
94 fn description(&self) -> &'static str {
95 "NFS file server"
96 }
97
98 fn example_prompt(&self) -> &'static str {
99 "Start an NFS file server on port 2049"
100 }
101
102 fn group_name(&self) -> &'static str {
103 "Web & File"
104 }
105
106 fn get_startup_examples(&self) -> crate::llm::actions::StartupExamples {
107 use crate::llm::actions::StartupExamples;
108
109 let script = r#"import json, sys
112data = json.load(sys.stdin)
113event = data["event"]
114if data["event_type_id"] == "nfs_operation":
115 actions = [{"type": "nfs_getattr_response",
116 "file_type": "directory", "mode": 493, "size": 4096}]
117else:
118 actions = []
119print(json.dumps({"actions": actions}))"#;
120
121 StartupExamples::new(
122 json!({
124 "type": "open_server",
125 "port": 2049,
126 "base_stack": "nfs",
127 "instruction": "NFS file server. On file reads, return content based on the path. On writes, acknowledge with updated attributes. Provide directory listings with sample files."
128 }),
129 json!({
131 "type": "open_server",
132 "port": 2049,
133 "base_stack": "nfs",
134 "event_handlers": [{
135 "event_pattern": "nfs_operation",
136 "handler": {
137 "type": "script",
138 "language": "python",
139 "code": script
140 }
141 }]
142 }),
143 json!({
145 "type": "open_server",
146 "port": 2049,
147 "base_stack": "nfs",
148 "event_handlers": [{
149 "event_pattern": "nfs_operation",
150 "handler": {
151 "type": "static",
152 "actions": [{
153 "type": "nfs_getattr_response",
154 "file_type": "regular",
155 "mode": 420,
156 "size": 1024,
157 "uid": 1000,
158 "gid": 1000
159 }]
160 }
161 }]
162 }),
163 )
164 }
165}
166
167impl Server for NfsProtocol {
168 fn spawn(
169 &self,
170 ctx: crate::protocol::SpawnContext,
171 ) -> std::pin::Pin<
172 Box<dyn std::future::Future<Output = anyhow::Result<std::net::SocketAddr>> + Send>,
173 > {
174 Box::pin(async move {
175 use crate::server::nfs::NfsServer;
176 NfsServer::spawn_with_llm_actions(
177 ctx.legacy_listen_addr(),
178 ctx.llm_client,
179 ctx.state,
180 ctx.status_tx,
181 ctx.server_id,
182 )
183 .await
184 })
185 }
186
187 fn execute_action(&self, action: serde_json::Value) -> Result<ActionResult> {
188 let action_type = action
189 .get("type")
190 .and_then(|v| v.as_str())
191 .context("Missing 'type' field in action")?;
192
193 match action_type {
194 "nfs_lookup_response" => self.execute_nfs_lookup_response(action),
195 "nfs_read_response" => self.execute_nfs_read_response(action),
196 "nfs_write_response" => self.execute_nfs_write_response(action),
197 "nfs_getattr_response" => self.execute_nfs_getattr_response(action),
198 "nfs_create_response" => self.execute_nfs_create_response(action),
199 "nfs_remove_response" => self.execute_nfs_remove_response(action),
200 "nfs_mkdir_response" => self.execute_nfs_mkdir_response(action),
201 "nfs_readdir_response" => self.execute_nfs_readdir_response(action),
202 "nfs_rename_response" => self.execute_nfs_rename_response(action),
203 "nfs_setattr_response" => self.execute_nfs_setattr_response(action),
204 _ => Err(anyhow::anyhow!("Unknown NFS action: {}", action_type)),
205 }
206 }
207}
208
209impl NfsProtocol {
210 fn execute_nfs_lookup_response(&self, action: serde_json::Value) -> Result<ActionResult> {
212 let response = json!({
213 "fileid": action.get("fileid").and_then(|v| v.as_u64()),
214 "error": action.get("error").and_then(|v| v.as_str()),
215 });
216 Ok(ActionResult::Output(serde_json::to_vec(&response)?))
217 }
218
219 fn execute_nfs_read_response(&self, action: serde_json::Value) -> Result<ActionResult> {
221 let response = json!({
222 "data": action.get("data").and_then(|v| v.as_str()).unwrap_or(""),
223 "eof": action.get("eof").and_then(|v| v.as_bool()).unwrap_or(true),
224 "error": action.get("error").and_then(|v| v.as_str()),
225 });
226 Ok(ActionResult::Output(serde_json::to_vec(&response)?))
227 }
228
229 fn execute_nfs_write_response(&self, action: serde_json::Value) -> Result<ActionResult> {
231 let response = json!({
232 "size": action.get("size").and_then(|v| v.as_u64()).unwrap_or(0),
233 "mode": action.get("mode").and_then(|v| v.as_u64()).unwrap_or(0o644),
234 "mtime": action.get("mtime").and_then(|v| v.as_u64()),
235 "error": action.get("error").and_then(|v| v.as_str()),
236 });
237 Ok(ActionResult::Output(serde_json::to_vec(&response)?))
238 }
239
240 fn execute_nfs_getattr_response(&self, action: serde_json::Value) -> Result<ActionResult> {
242 let response = json!({
243 "file_type": action.get("file_type").and_then(|v| v.as_str()).unwrap_or("regular"),
244 "mode": action.get("mode").and_then(|v| v.as_u64()).unwrap_or(0o644),
245 "size": action.get("size").and_then(|v| v.as_u64()).unwrap_or(0),
246 "uid": action.get("uid").and_then(|v| v.as_u64()).unwrap_or(0),
247 "gid": action.get("gid").and_then(|v| v.as_u64()).unwrap_or(0),
248 "atime": action.get("atime").and_then(|v| v.as_u64()),
249 "mtime": action.get("mtime").and_then(|v| v.as_u64()),
250 "ctime": action.get("ctime").and_then(|v| v.as_u64()),
251 "error": action.get("error").and_then(|v| v.as_str()),
252 });
253 Ok(ActionResult::Output(serde_json::to_vec(&response)?))
254 }
255
256 fn execute_nfs_create_response(&self, action: serde_json::Value) -> Result<ActionResult> {
258 let response = json!({
259 "fileid": action.get("fileid").and_then(|v| v.as_u64()),
260 "size": action.get("size").and_then(|v| v.as_u64()).unwrap_or(0),
261 "mode": action.get("mode").and_then(|v| v.as_u64()).unwrap_or(0o644),
262 "error": action.get("error").and_then(|v| v.as_str()),
263 });
264 Ok(ActionResult::Output(serde_json::to_vec(&response)?))
265 }
266
267 fn execute_nfs_remove_response(&self, action: serde_json::Value) -> Result<ActionResult> {
269 let response = json!({
270 "success": action.get("success").and_then(|v| v.as_bool()).unwrap_or(false),
271 "error": action.get("error").and_then(|v| v.as_str()),
272 });
273 Ok(ActionResult::Output(serde_json::to_vec(&response)?))
274 }
275
276 fn execute_nfs_mkdir_response(&self, action: serde_json::Value) -> Result<ActionResult> {
278 let response = json!({
279 "fileid": action.get("fileid").and_then(|v| v.as_u64()),
280 "mode": action.get("mode").and_then(|v| v.as_u64()).unwrap_or(0o755),
281 "error": action.get("error").and_then(|v| v.as_str()),
282 });
283 Ok(ActionResult::Output(serde_json::to_vec(&response)?))
284 }
285
286 fn execute_nfs_readdir_response(&self, action: serde_json::Value) -> Result<ActionResult> {
288 let response = json!({
289 "entries": action.get("entries").and_then(|v| v.as_array()).cloned().unwrap_or_default(),
290 "eof": action.get("eof").and_then(|v| v.as_bool()).unwrap_or(true),
291 "error": action.get("error").and_then(|v| v.as_str()),
292 });
293 Ok(ActionResult::Output(serde_json::to_vec(&response)?))
294 }
295
296 fn execute_nfs_rename_response(&self, action: serde_json::Value) -> Result<ActionResult> {
298 let response = json!({
299 "success": action.get("success").and_then(|v| v.as_bool()).unwrap_or(false),
300 "error": action.get("error").and_then(|v| v.as_str()),
301 });
302 Ok(ActionResult::Output(serde_json::to_vec(&response)?))
303 }
304
305 fn execute_nfs_setattr_response(&self, action: serde_json::Value) -> Result<ActionResult> {
307 let response = json!({
308 "size": action.get("size").and_then(|v| v.as_u64()),
309 "mode": action.get("mode").and_then(|v| v.as_u64()),
310 "mtime": action.get("mtime").and_then(|v| v.as_u64()),
311 "error": action.get("error").and_then(|v| v.as_str()),
312 });
313 Ok(ActionResult::Output(serde_json::to_vec(&response)?))
314 }
315}
316
317fn nfs_response_actions() -> Vec<ActionDefinition> {
324 vec![
325 nfs_lookup_response_action(),
326 nfs_getattr_response_action(),
327 nfs_setattr_response_action(),
328 nfs_read_response_action(),
329 nfs_write_response_action(),
330 nfs_create_response_action(),
331 nfs_mkdir_response_action(),
332 nfs_remove_response_action(),
333 nfs_rename_response_action(),
334 nfs_readdir_response_action(),
335 ]
336}
337
338fn nfs_lookup_response_action() -> ActionDefinition {
340 ActionDefinition {
341 name: "nfs_lookup_response".to_string(),
342 description: "Return file ID for NFS LOOKUP operation".to_string(),
343 parameters: vec![
344 Parameter {
345 name: "fileid".to_string(),
346 type_hint: "number".to_string(),
347 description: "File ID (unique identifier) if file exists".to_string(),
348 required: false,
349 },
350 Parameter {
351 name: "error".to_string(),
352 type_hint: "string".to_string(),
353 description:
354 "NFS error code if operation failed (e.g. 'NFS3ERR_NOENT', 'NFS3ERR_ACCES')"
355 .to_string(),
356 required: false,
357 },
358 ],
359 example: json!({
360 "type": "nfs_lookup_response",
361 "fileid": 42
362 }),
363 log_template: Some(
364 LogTemplate::new()
365 .with_info("-> NFS lookup fileid={fileid}")
366 .with_debug("NFS nfs_lookup_response: fileid={fileid}"),
367 ),
368 }
369}
370
371fn nfs_read_response_action() -> ActionDefinition {
372 ActionDefinition {
373 name: "nfs_read_response".to_string(),
374 description: "Return file data for NFS READ operation".to_string(),
375 parameters: vec![
376 Parameter {
377 name: "data".to_string(),
378 type_hint: "string".to_string(),
379 description:
380 "File content to return, as text. Sent to the client verbatim as UTF-8 \
381 bytes; there is no binary or encoded form, so binary files cannot be \
382 served."
383 .to_string(),
384 required: true,
385 },
386 Parameter {
387 name: "eof".to_string(),
388 type_hint: "boolean".to_string(),
389 description: "True if end of file reached".to_string(),
390 required: true,
391 },
392 Parameter {
393 name: "error".to_string(),
394 type_hint: "string".to_string(),
395 description: "NFS error code if operation failed".to_string(),
396 required: false,
397 },
398 ],
399 example: json!({
400 "type": "nfs_read_response",
401 "data": "Hello from NFS!",
402 "eof": false
403 }),
404 log_template: Some(
405 LogTemplate::new()
406 .with_info("-> NFS read data")
407 .with_debug("NFS nfs_read_response: eof={eof}"),
408 ),
409 }
410}
411
412fn nfs_write_response_action() -> ActionDefinition {
413 ActionDefinition {
414 name: "nfs_write_response".to_string(),
415 description: "Return file attributes after NFS WRITE operation".to_string(),
416 parameters: vec![
417 Parameter {
418 name: "size".to_string(),
419 type_hint: "number".to_string(),
420 description: "New file size after write".to_string(),
421 required: true,
422 },
423 Parameter {
424 name: "mode".to_string(),
425 type_hint: "number".to_string(),
426 description: "File permissions as a decimal number (420 = 0644, 493 = 0755)"
427 .to_string(),
428 required: false,
429 },
430 Parameter {
431 name: "mtime".to_string(),
432 type_hint: "number".to_string(),
433 description: "Modification time (Unix timestamp)".to_string(),
434 required: false,
435 },
436 Parameter {
437 name: "error".to_string(),
438 type_hint: "string".to_string(),
439 description: "NFS error code if operation failed".to_string(),
440 required: false,
441 },
442 ],
443 example: json!({
444 "type": "nfs_write_response",
445 "size": 1024,
446 "mode": 420
447 }),
448 log_template: Some(
449 LogTemplate::new()
450 .with_info("-> NFS write size={size}")
451 .with_debug("NFS nfs_write_response: size={size}, mode={mode}"),
452 ),
453 }
454}
455
456fn nfs_getattr_response_action() -> ActionDefinition {
457 ActionDefinition {
458 name: "nfs_getattr_response".to_string(),
459 description: "Return file/directory attributes for NFS GETATTR operation".to_string(),
460 parameters: vec![
461 Parameter {
462 name: "file_type".to_string(),
463 type_hint: "string".to_string(),
464 description: "'regular' for file, 'directory' for dir".to_string(),
465 required: true,
466 },
467 Parameter {
468 name: "mode".to_string(),
469 type_hint: "number".to_string(),
470 description:
471 "Permissions as a decimal number (420 = 0644 for files, 493 = 0755 for dirs)"
472 .to_string(),
473 required: true,
474 },
475 Parameter {
476 name: "size".to_string(),
477 type_hint: "number".to_string(),
478 description: "File size in bytes".to_string(),
479 required: true,
480 },
481 Parameter {
482 name: "uid".to_string(),
483 type_hint: "number".to_string(),
484 description: "Owner user ID".to_string(),
485 required: false,
486 },
487 Parameter {
488 name: "gid".to_string(),
489 type_hint: "number".to_string(),
490 description: "Owner group ID".to_string(),
491 required: false,
492 },
493 Parameter {
494 name: "atime".to_string(),
495 type_hint: "number".to_string(),
496 description: "Access time (Unix timestamp)".to_string(),
497 required: false,
498 },
499 Parameter {
500 name: "mtime".to_string(),
501 type_hint: "number".to_string(),
502 description: "Modification time (Unix timestamp)".to_string(),
503 required: false,
504 },
505 Parameter {
506 name: "ctime".to_string(),
507 type_hint: "number".to_string(),
508 description: "Status change time (Unix timestamp)".to_string(),
509 required: false,
510 },
511 Parameter {
512 name: "error".to_string(),
513 type_hint: "string".to_string(),
514 description: "NFS error code if operation failed".to_string(),
515 required: false,
516 },
517 ],
518 example: json!({
519 "type": "nfs_getattr_response",
520 "file_type": "regular",
521 "mode": 420,
522 "size": 1024,
523 "uid": 1000,
524 "gid": 1000
525 }),
526 log_template: Some(
527 LogTemplate::new()
528 .with_info("-> NFS attr {file_type} size={size}")
529 .with_debug("NFS nfs_getattr_response: type={file_type}, size={size}, mode={mode}"),
530 ),
531 }
532}
533
534fn nfs_create_response_action() -> ActionDefinition {
535 ActionDefinition {
536 name: "nfs_create_response".to_string(),
537 description: "Return new file ID and attributes for NFS CREATE operation".to_string(),
538 parameters: vec![
539 Parameter {
540 name: "fileid".to_string(),
541 type_hint: "number".to_string(),
542 description: "New file ID".to_string(),
543 required: true,
544 },
545 Parameter {
546 name: "size".to_string(),
547 type_hint: "number".to_string(),
548 description: "Initial file size (usually 0)".to_string(),
549 required: false,
550 },
551 Parameter {
552 name: "mode".to_string(),
553 type_hint: "number".to_string(),
554 description: "File permissions".to_string(),
555 required: false,
556 },
557 Parameter {
558 name: "error".to_string(),
559 type_hint: "string".to_string(),
560 description: "NFS error code if operation failed".to_string(),
561 required: false,
562 },
563 ],
564 example: json!({
565 "type": "nfs_create_response",
566 "fileid": 123,
567 "size": 0,
568 "mode": 420
569 }),
570 log_template: Some(
571 LogTemplate::new()
572 .with_info("-> NFS create fileid={fileid}")
573 .with_debug("NFS nfs_create_response: fileid={fileid}, mode={mode}"),
574 ),
575 }
576}
577
578fn nfs_remove_response_action() -> ActionDefinition {
579 ActionDefinition {
580 name: "nfs_remove_response".to_string(),
581 description: "Return success/error for NFS REMOVE operation".to_string(),
582 parameters: vec![
583 Parameter {
584 name: "success".to_string(),
585 type_hint: "boolean".to_string(),
586 description: "True if file was removed successfully".to_string(),
587 required: true,
588 },
589 Parameter {
590 name: "error".to_string(),
591 type_hint: "string".to_string(),
592 description: "NFS error code if operation failed".to_string(),
593 required: false,
594 },
595 ],
596 example: json!({
597 "type": "nfs_remove_response",
598 "success": true
599 }),
600 log_template: Some(
601 LogTemplate::new()
602 .with_info("-> NFS remove success={success}")
603 .with_debug("NFS nfs_remove_response: success={success}"),
604 ),
605 }
606}
607
608fn nfs_mkdir_response_action() -> ActionDefinition {
609 ActionDefinition {
610 name: "nfs_mkdir_response".to_string(),
611 description: "Return new directory ID for NFS MKDIR operation".to_string(),
612 parameters: vec![
613 Parameter {
614 name: "fileid".to_string(),
615 type_hint: "number".to_string(),
616 description: "New directory ID".to_string(),
617 required: true,
618 },
619 Parameter {
620 name: "mode".to_string(),
621 type_hint: "number".to_string(),
622 description: "Directory permissions as a decimal number (default 493 = 0755)"
623 .to_string(),
624 required: false,
625 },
626 Parameter {
627 name: "error".to_string(),
628 type_hint: "string".to_string(),
629 description: "NFS error code if operation failed".to_string(),
630 required: false,
631 },
632 ],
633 example: json!({
634 "type": "nfs_mkdir_response",
635 "fileid": 456,
636 "mode": 493
637 }),
638 log_template: Some(
639 LogTemplate::new()
640 .with_info("-> NFS mkdir fileid={fileid}")
641 .with_debug("NFS nfs_mkdir_response: fileid={fileid}, mode={mode}"),
642 ),
643 }
644}
645
646fn nfs_readdir_response_action() -> ActionDefinition {
647 ActionDefinition {
648 name: "nfs_readdir_response".to_string(),
649 description: "Return directory listing for NFS READDIR operation".to_string(),
650 parameters: vec![
651 Parameter {
652 name: "entries".to_string(),
653 type_hint: "array".to_string(),
654 description:
655 "Array of directory entries [{\"name\": \"file.txt\", \"fileid\": 42}, ...]"
656 .to_string(),
657 required: true,
658 },
659 Parameter {
660 name: "eof".to_string(),
661 type_hint: "boolean".to_string(),
662 description: "True if no more entries".to_string(),
663 required: true,
664 },
665 Parameter {
666 name: "error".to_string(),
667 type_hint: "string".to_string(),
668 description: "NFS error code if operation failed".to_string(),
669 required: false,
670 },
671 ],
672 example: json!({
673 "type": "nfs_readdir_response",
674 "entries": [
675 {"name": "file.txt", "fileid": 42},
676 {"name": "subdir", "fileid": 43}
677 ],
678 "eof": true
679 }),
680 log_template: Some(
681 LogTemplate::new()
682 .with_info("-> NFS readdir {entries_len} entries")
683 .with_debug("NFS nfs_readdir_response: {entries_len} entries, eof={eof}"),
684 ),
685 }
686}
687
688fn nfs_rename_response_action() -> ActionDefinition {
689 ActionDefinition {
690 name: "nfs_rename_response".to_string(),
691 description: "Return success/error for NFS RENAME operation".to_string(),
692 parameters: vec![
693 Parameter {
694 name: "success".to_string(),
695 type_hint: "boolean".to_string(),
696 description: "True if file was renamed successfully".to_string(),
697 required: true,
698 },
699 Parameter {
700 name: "error".to_string(),
701 type_hint: "string".to_string(),
702 description: "NFS error code if operation failed".to_string(),
703 required: false,
704 },
705 ],
706 example: json!({
707 "type": "nfs_rename_response",
708 "success": true
709 }),
710 log_template: Some(
711 LogTemplate::new()
712 .with_info("-> NFS rename success={success}")
713 .with_debug("NFS nfs_rename_response: success={success}"),
714 ),
715 }
716}
717
718fn nfs_setattr_response_action() -> ActionDefinition {
719 ActionDefinition {
720 name: "nfs_setattr_response".to_string(),
721 description: "Return updated attributes for NFS SETATTR operation".to_string(),
722 parameters: vec![
723 Parameter {
724 name: "size".to_string(),
725 type_hint: "number".to_string(),
726 description: "New file size".to_string(),
727 required: false,
728 },
729 Parameter {
730 name: "mode".to_string(),
731 type_hint: "number".to_string(),
732 description: "New permissions".to_string(),
733 required: false,
734 },
735 Parameter {
736 name: "mtime".to_string(),
737 type_hint: "number".to_string(),
738 description: "New modification time".to_string(),
739 required: false,
740 },
741 Parameter {
742 name: "error".to_string(),
743 type_hint: "string".to_string(),
744 description: "NFS error code if operation failed".to_string(),
745 required: false,
746 },
747 ],
748 example: json!({
749 "type": "nfs_setattr_response",
750 "mode": 384
751 }),
752 log_template: Some(
753 LogTemplate::new()
754 .with_info("-> NFS setattr")
755 .with_debug("NFS nfs_setattr_response: mode={mode}"),
756 ),
757 }
758}
759
760pub static NFS_OPERATION_EVENT: LazyLock<EventType> = LazyLock::new(|| {
772 EventType::new(
773 "nfs_operation",
774 "NFS client requested a filesystem operation",
775 json!({"type": "nfs_getattr_response", "file_type": "regular", "mode": 420, "size": 13}),
776 )
777 .with_parameters(vec![
778 Parameter {
779 name: "operation".to_string(),
780 type_hint: "string".to_string(),
781 description: "The NFS operation type (lookup, getattr, setattr, read, write, create, mkdir, remove, rename, readdir, symlink, readlink)".to_string(),
782 required: true,
783 },
784 Parameter {
785 name: "params".to_string(),
786 type_hint: "object".to_string(),
787 description: "Operation-specific parameters (fileid, dirid, filename, offset, count, data, ...)".to_string(),
788 required: true,
789 },
790 ])
791 .with_actions(nfs_response_actions())
794 .with_alternative_example(json!({
795 "type": "nfs_lookup_response",
796 "fileid": 2
797 }))
798 .with_alternative_example(json!({
799 "type": "nfs_read_response",
800 "data": "Hello from NFS",
801 "eof": true
802 }))
803 .with_alternative_example(json!({
804 "type": "nfs_readdir_response",
805 "entries": [{"name": "readme.txt", "fileid": 2}],
806 "eof": true
807 }))
808 .with_log_template(
809 LogTemplate::new()
810 .with_info("NFS {operation}")
811 .with_debug("NFS {operation}: {params}")
812 .with_trace("NFS: {json_pretty(.)}"),
813 )
814});
815
816pub fn get_nfs_event_types() -> Vec<EventType> {
818 vec![NFS_OPERATION_EVENT.clone()]
819}