master
cpp 1,489 lines 49.5 KB
Raw
1 // Copyright (C) Microsoft Corporation. All rights reserved.
2 #include "precomp.h"
3 #include "p9protohelpers.h"
4 #include "p9errors.h"
5 #include "p9defs.h"
6 #include "p9tracelogging.h"
7 #include "p9tracelogginghelper.h"
8 #include "p9log.h"
9 #include "p9data.h"
10 #include "p9await.h"
11 #include "p9fid.h"
12 #include "p9handler.h"
13 #include "p9commonutil.h"
14
15 namespace p9fs {
16
17 // Size to use for the stack-allocated response buffer.
18 constexpr UINT32 c_staticBufferSize = 256;
19
20 constexpr UINT32 c_createRetryCount = 3;
21
22 // Handler for 9pfs protocol messages.
23 class Handler final : public IHandler
24 {
25 public:
26 Handler(ISocket& s, IShareList& shareList) noexcept :
27 m_Socket{&s}, m_Requests{std::make_shared<RequestList>()}, m_ShareList{shareList}
28 {
29 }
30
31 Handler(IShareList& shareList, bool allowRenegotiate) noexcept :
32 m_Requests{std::make_shared<RequestList>()}, m_AllowRenegotiate{allowRenegotiate}, m_ShareList{shareList}
33 {
34 }
35
36 private:
37 // Encapsulates the buffer and SpanWriter used for sending a response to the client.
38 class MessageResponse final
39 {
40 public:
41 // Initializes a new MessageResponse with the specified buffer.
42 MessageResponse(gsl::span<gsl::byte> initialBuffer, bool allowResize = true) :
43 Writer{initialBuffer}, m_allowResize{allowResize}
44 {
45 // Skip the header, which will be written last.
46 Writer.Next(HeaderSize);
47 }
48
49 // Checks if the current buffer is large enough for the message, taking any additional
50 // dynamic values into account. If not, a new buffer is allocated and used to write the
51 // response.
52 void EnsureSize(MessageType message, UINT32 extraSize, UINT32 maxSize)
53 {
54 // Ensure this function is called for a response message (which are always odd).
55 WI_ASSERT(static_cast<int>(message) % 2 == 1);
56
57 auto size = static_cast<UINT64>(GetMessageSize(message)) + extraSize;
58
59 // Check if the message is larger than the negotiated size. This could happen if the
60 // client is sending invalid requests.
61 if (size > maxSize)
62 {
63 THROW_INVALID();
64 }
65
66 // If the message is larger than the stack buffer, allocate a dynamic buffer and
67 // update the writer.
68 // N.B. This is not allowed if the initial buffer was based on a virtio write span.
69 if (size > Writer.MaxSize())
70 {
71 if (!m_allowResize)
72 {
73 Plan9TraceLoggingProvider::InvalidResponseBufferSize();
74 THROW_INVALID();
75 }
76
77 m_dynamicBuffer.resize(size);
78 Writer = SpanWriter{m_dynamicBuffer};
79
80 // Skip the header, which will be written last.
81 Writer.Next(HeaderSize);
82 }
83 }
84
85 SpanWriter Writer;
86
87 private:
88 MessageResponse(const MessageResponse&) = delete;
89 MessageResponse& operator=(const MessageResponse&) = delete;
90
91 std::vector<gsl::byte> m_dynamicBuffer;
92 bool m_allowResize;
93 };
94
95 // Encapsulates information about a request in progress, which is used by Tflush to wait
96 // for completion before responding.
97 struct RequestInfo
98 {
99 RequestInfo(UINT16 tag) : Tag{tag}
100 {
101 }
102
103 // Since this is part of a linked list, make sure it's never moved or copied.
104 RequestInfo(const RequestInfo&) = delete;
105 RequestInfo& operator=(const RequestInfo&) = delete;
106 RequestInfo(RequestInfo&&) = delete;
107 RequestInfo& operator=(RequestInfo&&) = delete;
108
109 LIST_ENTRY Link;
110 AsyncEvent Event;
111 UINT16 Tag;
112 bool Cancelled{};
113 };
114
115 struct RequestList
116 {
117 std::mutex Lock;
118 util::LinkedList<RequestInfo> Requests;
119 };
120
121 class RequestTracker
122 {
123 public:
124 RequestTracker(const std::shared_ptr<RequestList>& requests, UINT16 tag) :
125 m_requestList{requests}, m_request{std::make_unique<RequestInfo>(tag)}
126 {
127 // Insert into the list on construction.
128 std::lock_guard<std::mutex> lock{m_requestList->Lock};
129 m_requestList->Requests.Insert(*m_request);
130 }
131
132 RequestTracker(RequestTracker&& other) = default;
133
134 ~RequestTracker()
135 {
136 // The pointers can be invalid if this instance has been moved.
137 if (!m_requestList || !m_request)
138 {
139 return;
140 }
141
142 {
143 std::lock_guard<std::mutex> lock{m_requestList->Lock};
144
145 // Remove the request from the list of pending requests. This means that Cancelled
146 // can't change after the lock is dropped, since HandleFlush can't find the request
147 // anymore.
148 m_requestList->Requests.Remove(*m_request);
149 }
150
151 // If the request is marked Cancelled, it means a Tflush has taken ownership of this
152 // pointer and is waiting for the event. The pointer may become invalid as soon as the
153 // event is set.
154 // N.B. A shared_ptr would be nicer, but Tflush can only get the RequestInfo itself
155 // from the linked list, so it wouldn't be able to access the shared_ptr's
156 // reference count. Therefore, this ownership shuffling is required.
157 auto& localRequest = *m_request;
158 if (m_request->Cancelled)
159 {
160 m_request.release();
161 }
162
163 localRequest.Event.Set();
164 }
165
166 RequestInfo& Request() const
167 {
168 return *m_request;
169 }
170
171 private:
172 std::shared_ptr<RequestList> m_requestList;
173 std::unique_ptr<RequestInfo> m_request;
174 };
175
176 void LogMessage([[maybe_unused]] gsl::span<const gsl::byte> message)
177 {
178 TraceLogMessage(message);
179 }
180
181 Task<LX_INT> HandleMessage(MessageType messageType, SpanReader& reader, MessageResponse& response)
182 {
183 // Handle async operations.
184 switch (messageType)
185 {
186 case MessageType::Tread:
187 co_return co_await HandleRead(reader, response);
188
189 case MessageType::Twrite:
190 co_return co_await HandleWrite(reader, response);
191
192 case MessageType::Tflush:
193 co_return co_await HandleFlush(reader);
194
195 default:
196 // Default label prevents warning in clang.
197 break;
198 }
199
200 // Handle blocking operations.
201 co_return co_await BlockingCode([&]() -> LX_INT {
202 switch (messageType)
203 {
204 case MessageType::Tstatfs:
205 return HandleStatFs(reader, response);
206
207 case MessageType::Tlopen:
208 return HandleLOpen(reader, response);
209
210 case MessageType::Tlcreate:
211 return HandleLCreate(reader, response);
212
213 case MessageType::Tsymlink:
214 return HandleSymLink(reader, response);
215
216 case MessageType::Tmknod:
217 return HandleMkNod(reader, response);
218
219 case MessageType::Treadlink:
220 return HandleReadLink(reader, response);
221
222 case MessageType::Tgetattr:
223 return HandleGetAttr(reader, response);
224
225 case MessageType::Tsetattr:
226 return HandleSetAttr(reader);
227
228 case MessageType::Txattrwalk:
229 return HandleXattrWalk(reader, response);
230
231 case MessageType::Txattrcreate:
232 return HandleXattrCreate(reader);
233
234 case MessageType::Treaddir:
235 case MessageType::Twreaddir:
236 return HandleReadDir(reader, response, messageType == MessageType::Twreaddir);
237
238 case MessageType::Tfsync:
239 return HandleFsync(reader);
240
241 case MessageType::Tlock:
242 return HandleLock(reader, response);
243
244 case MessageType::Tgetlock:
245 return HandleGetLock(reader, response);
246
247 case MessageType::Tlink:
248 return HandleLink(reader);
249
250 case MessageType::Tmkdir:
251 return HandleMkDir(reader, response);
252
253 case MessageType::Trenameat:
254 return HandleRenameAt(reader);
255
256 case MessageType::Tunlinkat:
257 return HandleUnlinkAt(reader);
258
259 case MessageType::Tversion:
260 return HandleVersion(reader, response);
261
262 case MessageType::Tauth:
263 return HandleNotSupported("auth");
264
265 case MessageType::Tattach:
266 return HandleAttach(reader, response);
267
268 case MessageType::Twalk:
269 return HandleWalk(reader, response);
270
271 case MessageType::Tclunk:
272 return HandleClunk(reader);
273
274 case MessageType::Tremove:
275 return HandleRemove(reader);
276
277 case MessageType::Trename:
278 return HandleRename(reader);
279
280 case MessageType::Taccess:
281 return HandleAccess(reader);
282
283 case MessageType::Twopen:
284 return HandleWOpen(reader, response);
285
286 default:
287 return LX_ENOTSUP;
288 }
289 });
290 }
291
292 LX_INT HandleNotSupported(PCSTR)
293 {
294 return LX_ENOTSUP;
295 }
296
297 LX_INT HandleVersion(SpanReader& reader, MessageResponse& response)
298 {
299 // Tversion can only be sent once, unless it's specifically allowed multiple times which
300 // is used for virtio.
301 if (m_Negotiated && !m_AllowRenegotiate)
302 {
303 return LX_ENOTSUP;
304 }
305
306 auto size = reader.U32();
307 auto version = reader.String();
308
309 if (size < MinimumRequestBufferSize)
310 {
311 return LX_ENOTSUP;
312 }
313
314 bool use9P2000W = false;
315 if (version == ProtocolVersionW)
316 {
317 use9P2000W = true;
318 }
319 else if (version != ProtocolVersionL)
320 {
321 return LX_ENOTSUP;
322 }
323
324 size = std::min(size, MaximumRequestBufferSize);
325
326 // If Tversion was allowed more than once, still require the values to match the previously
327 // negotiated values.
328 if (m_Negotiated && (use9P2000W != m_Use9P2000W || size != m_NegotiatedSize))
329 {
330 return LX_ENOTSUP;
331 }
332
333 m_Use9P2000W = use9P2000W;
334 m_NegotiatedSize = size;
335 m_Negotiated = true;
336 response.EnsureSize(MessageType::Rversion, static_cast<UINT32>(version.size()), m_NegotiatedSize);
337 response.Writer.U32(size);
338 response.Writer.String(version);
339 return {};
340 }
341
342 LX_INT HandleAttach(SpanReader& reader, MessageResponse& response)
343 {
344 const auto fid = reader.U32();
345 reader.U32(); // afid (unused)
346 reader.String(); // uname (unused)
347 auto aname = reader.String();
348 auto uid = reader.U32();
349
350 auto root = m_ShareList.MakeRoot(std::string_view{aname.data(), gsl::narrow_cast<size_t>(aname.size())}, uid);
351 if (!root)
352 {
353 return root.Error();
354 }
355
356 auto result = CreateFile(root.Get(), uid);
357 if (!result)
358 {
359 return result.Error();
360 }
361
362 auto [file, qid] = result.Get();
363
364 EmplaceFid(fid, file);
365
366 response.EnsureSize(MessageType::Rattach, 0, m_NegotiatedSize);
367 response.Writer.Qid(qid);
368 return {};
369 }
370
371 LX_INT HandleStatFs(SpanReader& reader, MessageResponse& response)
372 {
373 const auto fid = reader.U32();
374
375 const auto file = LookupFid(fid);
376
377 auto result = file->StatFs();
378 if (!result)
379 {
380 return result.Error();
381 }
382
383 auto& statfs = result.Get();
384 response.EnsureSize(MessageType::Rstatfs, 0, m_NegotiatedSize);
385 response.Writer.U32(statfs.Type);
386 response.Writer.U32(statfs.BlockSize);
387 response.Writer.U64(statfs.Blocks);
388 response.Writer.U64(statfs.BlocksFree);
389 response.Writer.U64(statfs.BlocksAvailable);
390 response.Writer.U64(statfs.Files);
391 response.Writer.U64(statfs.FilesFree);
392 response.Writer.U64(statfs.FsId);
393 response.Writer.U32(statfs.NameLength);
394 return {};
395 }
396
397 LX_INT HandleGetAttr(SpanReader& reader, MessageResponse& response)
398 {
399 const auto fid = reader.U32();
400 const auto mask = reader.U64();
401 const auto file = LookupFid(fid);
402
403 auto result = file->GetAttr(mask);
404 if (!result)
405 {
406 return result.Error();
407 }
408
409 auto& [valid, qid, stat] = result.Get();
410 response.EnsureSize(MessageType::Rgetattr, 0, m_NegotiatedSize);
411 response.Writer.U64(valid);
412 response.Writer.Qid(qid);
413 util::SpanWriteStatResult(response.Writer, stat);
414 response.Writer.U64(0); // btime sec (reserved)
415 response.Writer.U64(0); // btime nsec (reserved)
416 response.Writer.U64(0); // gen (reserved)
417 response.Writer.U64(0); // data version (reserved)
418
419 return {};
420 }
421
422 LX_INT HandleWalk(SpanReader& reader, MessageResponse& response)
423 {
424 const auto fid = reader.U32();
425 const auto newfid = reader.U32();
426 const auto nameCount = reader.U16();
427 std::vector<std::string_view> names;
428 for (auto i = 0u; i < nameCount; ++i)
429 {
430 auto name = reader.Name();
431 names.push_back(name);
432 }
433
434 const auto entry = LookupFid(fid);
435 const auto newFile = entry->Clone();
436
437 response.EnsureSize(MessageType::Rwalk, nameCount * QidSize, m_NegotiatedSize);
438 response.Writer.U16(nameCount);
439 for (const auto& name : names)
440 {
441 auto qid = newFile->Walk(name);
442 if (!qid)
443 {
444 return qid.Error();
445 }
446
447 response.Writer.Qid(qid.Get());
448 }
449
450 EmplaceFid(newfid, newFile);
451 return {};
452 }
453
454 LX_INT HandleClunk(SpanReader& reader)
455 {
456 const auto fid = reader.U32();
457
458 std::shared_ptr<Fid> item;
459
460 {
461 std::lock_guard<std::shared_mutex> lock{m_FidsLock};
462 const auto iterator = m_Fids.find(fid);
463 if (iterator == m_Fids.end())
464 {
465 return LX_EINVAL;
466 }
467
468 item = std::move(iterator->second);
469 // Erase regardless of whether the clunk call succeeded.
470 m_Fids.erase(iterator);
471 }
472
473 return item->Clunk();
474 }
475
476 LX_INT HandleLOpen(SpanReader& reader, MessageResponse& response)
477 {
478 const auto fid = reader.U32();
479 auto flags = reader.U32();
480
481 const auto entry = LookupFid(fid);
482 auto qid = entry->Open(static_cast<OpenFlags>(flags));
483 if (!qid)
484 {
485 return qid.Error();
486 }
487
488 response.EnsureSize(MessageType::Rlopen, 0, m_NegotiatedSize);
489 response.Writer.Qid(qid.Get());
490 response.Writer.U32(IoUnit());
491 return {};
492 }
493
494 LX_INT HandleLCreate(SpanReader& reader, MessageResponse& response)
495 {
496 const auto fid = reader.U32();
497 auto name = reader.Name();
498 auto flags = reader.U32();
499 const auto mode = reader.U32();
500 const auto gid = reader.U32();
501
502 const auto file = LookupFid(fid);
503 auto qid = file->Create(name, static_cast<OpenFlags>(flags), mode, gid);
504 if (!qid)
505 {
506 return qid.Error();
507 }
508
509 response.EnsureSize(MessageType::Rlcreate, 0, m_NegotiatedSize);
510 response.Writer.Qid(qid.Get());
511 response.Writer.U32(IoUnit());
512 return {};
513 }
514
515 LX_INT HandleSymLink(SpanReader& reader, MessageResponse& response)
516 {
517 const auto fid = reader.U32();
518 auto name = reader.Name();
519 auto target = reader.String();
520 const auto gid = reader.U32();
521
522 const auto file = LookupFid(fid);
523 auto result = file->SymLink(name, target, gid);
524 if (!result)
525 {
526 return result.Error();
527 }
528
529 response.EnsureSize(MessageType::Rsymlink, 0, m_NegotiatedSize);
530 response.Writer.Qid(result.Get());
531 return {};
532 }
533
534 LX_INT HandleMkNod(SpanReader& reader, MessageResponse& response)
535 {
536 const auto fid = reader.U32();
537 auto name = reader.Name();
538 const auto mode = reader.U32();
539 const auto major = reader.U32();
540 const auto minor = reader.U32();
541 const auto gid = reader.U32();
542
543 const auto file = LookupFid(fid);
544 auto result = file->MkNod(name, mode, major, minor, gid);
545 if (!result)
546 {
547 return result.Error();
548 }
549
550 response.EnsureSize(MessageType::Rmknod, 0, m_NegotiatedSize);
551 response.Writer.Qid(result.Get());
552 return {};
553 }
554
555 LX_INT HandleReadLink(SpanReader& reader, MessageResponse& response)
556 {
557 const auto fid = reader.U32();
558
559 const auto file = LookupFid(fid);
560
561 // The actual size of the symlink is unknown at this point, so allocate a buffer that is
562 // large enough for the biggest possible target.
563 response.EnsureSize(MessageType::Rreadlink, LX_PATH_MAX, m_NegotiatedSize);
564 auto buffer = response.Writer.Peek().subspan(sizeof(UINT16));
565 auto charSpan = gsl::span<char>(reinterpret_cast<char*>(buffer.data()), buffer.size());
566 auto result = file->ReadLink(charSpan);
567 if (!result)
568 {
569 return result.Error();
570 }
571
572 // Write the string length; we cannot use .String() because the string
573 // data has already been written.
574 response.Writer.U16(static_cast<UINT16>(result.Get()));
575 response.Writer.Next(result.Get());
576 return {};
577 }
578
579 LX_INT HandleReadDir(SpanReader& reader, MessageResponse& response, bool includeAttributes)
580 {
581 if (includeAttributes && !m_Use9P2000W)
582 {
583 return LX_ENOTSUP;
584 }
585
586 const auto fid = reader.U32();
587 const auto offset = reader.U64();
588 auto count = reader.U32();
589
590 const auto file = LookupFid(fid);
591
592 response.EnsureSize(MessageType::Rreaddir, count, m_NegotiatedSize);
593 SpanWriter direntWriter{response.Writer.Peek().subspan(sizeof(UINT32), count)};
594 auto error = file->ReadDir(offset, direntWriter, includeAttributes);
595 if (error != 0)
596 {
597 return error;
598 }
599
600 auto written = direntWriter.Result().size();
601 response.Writer.U32(static_cast<UINT32>(written));
602 response.Writer.Next(written);
603 return {};
604 }
605
606 LX_INT HandleFsync(SpanReader& reader)
607 {
608 const auto fid = reader.U32();
609
610 const auto file = LookupFid(fid);
611 return file->Fsync();
612 }
613
614 LX_INT HandleLink(SpanReader& reader)
615 {
616 const auto dfid = reader.U32();
617 const auto fid = reader.U32();
618 auto name = reader.Name();
619
620 auto [dir, file] = LookupFidPair(dfid, fid);
621 return dir->Link(name, *file);
622 }
623
624 Task<LX_INT> HandleRead(SpanReader& reader, MessageResponse& response)
625 {
626 const auto fid = reader.U32();
627 const auto offset = reader.U64();
628 const auto count = reader.U32();
629
630 const auto file = LookupFid(fid);
631 response.EnsureSize(MessageType::Rread, count, m_NegotiatedSize);
632 auto result = co_await file->Read(offset, response.Writer.Peek(sizeof(UINT32) + count).subspan(sizeof(UINT32)));
633 if (!result)
634 {
635 co_return result.Error();
636 }
637
638 response.Writer.U32(result.Get());
639 response.Writer.Next(result.Get());
640 co_return LX_INT{};
641 }
642
643 Task<LX_INT> HandleWrite(SpanReader& reader, MessageResponse& response)
644 {
645 const auto fid = reader.U32();
646 const auto offset = reader.U64();
647 const auto count = reader.U32();
648 auto data = reader.Read(count);
649
650 const auto file = LookupFid(fid);
651 auto result = co_await file->Write(offset, data);
652 if (!result)
653 {
654 co_return result.Error();
655 }
656
657 response.EnsureSize(MessageType::Rwrite, 0, m_NegotiatedSize);
658 response.Writer.U32(result.Get());
659 co_return LX_INT{};
660 }
661
662 LX_INT HandleUnlinkAt(SpanReader& reader)
663 {
664 const auto fid = reader.U32();
665 auto name = reader.Name();
666 const auto flags = reader.U32();
667
668 const auto file = LookupFid(fid);
669 return file->UnlinkAt(name, flags);
670 }
671
672 LX_INT HandleRemove(SpanReader& reader)
673 {
674 const auto fid = reader.U32();
675
676 const auto file = LookupFid(fid);
677 return file->Remove();
678 }
679
680 LX_INT HandleRenameAt(SpanReader& reader)
681 {
682 const auto oldfid = reader.U32();
683 auto oldname = reader.Name();
684 const auto newfid = reader.U32();
685 auto newname = reader.Name();
686
687 auto [olddir, newdir] = LookupFidPair(oldfid, newfid);
688 return olddir->RenameAt(oldname, *newdir, newname);
689 }
690
691 LX_INT HandleRename(SpanReader& reader)
692 {
693 const auto oldFid = reader.U32();
694 const auto newFid = reader.U32();
695 auto newName = reader.Name();
696
697 auto [oldFile, newDir] = LookupFidPair(oldFid, newFid);
698 return oldFile->Rename(*newDir, newName);
699 }
700
701 LX_INT HandleMkDir(SpanReader& reader, MessageResponse& response)
702 {
703 const auto fid = reader.U32();
704 auto name = reader.Name();
705 const auto mode = reader.U32();
706 const auto gid = reader.U32();
707
708 const auto file = LookupFid(fid);
709 auto result = file->MkDir(name, mode, gid);
710 if (!result)
711 {
712 return result.Error();
713 }
714
715 response.EnsureSize(MessageType::Rmkdir, 0, m_NegotiatedSize);
716 response.Writer.Qid(result.Get());
717 return {};
718 }
719
720 LX_INT HandleSetAttr(SpanReader& reader)
721 {
722 StatResult stat{};
723 const auto fid = reader.U32();
724 const auto valid = reader.U32();
725 stat.Mode = reader.U32();
726 stat.Uid = reader.U32();
727 stat.Gid = reader.U32();
728 stat.Size = reader.U64();
729 stat.AtimeSec = reader.U64();
730 stat.AtimeNsec = reader.U64();
731 stat.MtimeSec = reader.U64();
732 stat.MtimeNsec = reader.U64();
733
734 const auto file = LookupFid(fid);
735 return file->SetAttr(valid, stat);
736 }
737
738 LX_INT HandleLock(SpanReader& reader, MessageResponse& response)
739 {
740 const auto fid = reader.U32();
741 auto type = reader.U8();
742 const auto flags = reader.U32();
743 const auto start = reader.U64();
744 const auto length = reader.U64();
745 const auto procId = reader.U32();
746 auto clientId = reader.String();
747
748 const auto file = LookupFid(fid);
749 auto status = file->Lock(LockType{type}, flags, start, length, procId, clientId);
750 if (!status)
751 {
752 return status.Error();
753 }
754
755 response.EnsureSize(MessageType::Rlock, 0, m_NegotiatedSize);
756 response.Writer.U8(static_cast<UINT8>(status.Get()));
757 return {};
758 }
759
760 LX_INT HandleGetLock(SpanReader& reader, MessageResponse& response)
761 {
762 const auto fid = reader.U32();
763 auto type = reader.U8();
764 const auto start = reader.U64();
765 const auto length = reader.U64();
766 const auto procId = reader.U32();
767 auto clientId = reader.String();
768
769 const auto file = LookupFid(fid);
770 auto result = file->GetLock(LockType{type}, start, length, procId, clientId);
771 if (!result)
772 {
773 return result.Error();
774 }
775
776 auto [returnType, returnStart, returnLength, returnProcId, returnClientId] = result.Get();
777 response.EnsureSize(MessageType::Rgetlock, 0, m_NegotiatedSize);
778 response.Writer.U8(static_cast<UINT8>(returnType));
779 response.Writer.U64(returnStart);
780 response.Writer.U64(returnLength);
781 response.Writer.U32(returnProcId);
782 response.Writer.String(returnClientId);
783 return {};
784 }
785
786 LX_INT HandleXattrWalk(SpanReader& reader, MessageResponse& response)
787 {
788 const auto fid = reader.U32();
789 const auto newFid = reader.U32();
790 auto name = reader.String();
791
792 const auto entry = LookupFid(fid);
793 auto xattr = entry->XattrWalk(std::string{name.data(), static_cast<size_t>(name.size())});
794 if (!xattr)
795 {
796 return xattr.Error();
797 }
798
799 auto size = xattr.Get()->GetSize();
800 if (!size)
801 {
802 return size.Error();
803 }
804
805 EmplaceFid(newFid, xattr.Get());
806 response.EnsureSize(MessageType::Rxattrwalk, 0, m_NegotiatedSize);
807 response.Writer.U64(size.Get());
808 return {};
809 }
810
811 LX_INT HandleXattrCreate(SpanReader& reader)
812 {
813 const auto fid = reader.U32();
814 auto name = reader.String();
815 const auto size = reader.U64();
816 const auto flags = reader.U32();
817
818 const auto entry = LookupFid(fid);
819
820 const std::string nameString{name.data(), static_cast<size_t>(name.size())};
821 auto xattr = entry->XattrCreate(nameString, size, flags);
822 if (!xattr)
823 {
824 return xattr.Error();
825 }
826
827 // Unlike xattrwalk, xattrcreate updates the current fid, so replace
828 // it.
829 std::lock_guard<std::shared_mutex> lock{m_FidsLock};
830 const auto iterator = m_Fids.find(fid);
831 THROW_UNEXPECTED_IF((iterator == m_Fids.end()) || (iterator->second != entry));
832 iterator->second = xattr.Get();
833 return {};
834 }
835
836 LX_INT HandleAccess(SpanReader& reader)
837 {
838 if (!m_Use9P2000W)
839 {
840 return LX_ENOTSUP;
841 }
842
843 const auto fid = reader.U32();
844 auto flags = reader.U32();
845 const auto entry = LookupFid(fid);
846 return entry->Access(static_cast<AccessFlags>(flags));
847 }
848
849 // Handle the 9P2000.W Twopen message.
850 //
851 // This message combines the functionality of walk, open, create, mkdir, readlink, and getattr.
852 // Certain error conditions (a part of the path could not be found, or a component in the path
853 // was not a directory) are reported not using Rlerror, but using Rwopen with an appropriate
854 // status code. In this case, the response informs the caller how many components of the path
855 // were processed, and returns the attributes of the last successfully walked component.
856 //
857 // If a symlink is encountered in the path (including as the leaf component), its target is
858 // also returned. Whether a symlink as the leaf is treated as an error or success depends on
859 // whether OpenSymlink is specified.
860 //
861 // The return status indicates whether an existing file was opened or a new one was created.
862 // If a new file has to be created, this function creates a directory if O_DIRECTORY was
863 // specified.
864 //
865 // Only if the response indicates the status Opened or Created is the "newfid" argument used,
866 // and needs to be clunked. With any other status, the client can reuse that fid immediately.
867 LX_INT HandleWOpen(SpanReader& reader, MessageResponse& response)
868 {
869 if (!m_Use9P2000W)
870 {
871 return LX_ENOTSUP;
872 }
873
874 const auto fid = reader.U32();
875 const auto newFid = reader.U32();
876 auto flags = static_cast<OpenFlags>(reader.U32());
877 auto wflags = static_cast<WOpenFlags>(reader.U32());
878 const auto mode = reader.U32();
879 const auto gid = reader.U32();
880 const auto attrMask = reader.U64();
881 const auto nameCount = reader.U16();
882
883 const auto entry = LookupFid(fid);
884 const auto newFile = entry->Clone();
885
886 bool exists = false;
887 bool needOpen = true;
888 Qid entryQid = newFile->GetQid();
889 if (nameCount > 0)
890 {
891 // Step 1: Find the parent of the final item.
892 for (UINT16 i = 0; i < nameCount - 1; ++i)
893 {
894 auto qid = newFile->Walk(reader.Name());
895 if (!qid)
896 {
897 // For ENOENT and ENOTDIR, indicate how many components were processed.
898 switch (qid.Error())
899 {
900 case LX_ENOENT:
901 return WriteWOpenReply(WOpenStatus::ParentNotFound, i, *newFile, attrMask, response);
902
903 case LX_ENOTDIR:
904 return WriteWOpenReply(WOpenStatus::Stopped, i, *newFile, attrMask, response);
905
906 default:
907 return qid.Error();
908 }
909 }
910 }
911
912 auto name = reader.Name();
913
914 // Step 2: Find the item, unless it's an exclusive create.
915 int retries;
916 for (retries = 0; retries < c_createRetryCount; ++retries)
917 {
918 if (!WI_AreAllFlagsSet(flags, OpenFlags::Create | OpenFlags::Exclusive))
919 {
920 auto qid = newFile->Walk(name);
921 if (!qid)
922 {
923 // For ENOENT (only if not creating) and ENOTDIR, indicate how many components were processed.
924 switch (qid.Error())
925 {
926 case LX_ENOENT:
927 if (!WI_IsFlagSet(flags, OpenFlags::Create))
928 {
929 return WriteWOpenReply(WOpenStatus::NotFound, nameCount - 1, *newFile, attrMask, response);
930 }
931
932 break;
933
934 case LX_ENOTDIR:
935 return WriteWOpenReply(WOpenStatus::Stopped, nameCount - 1, *newFile, attrMask, response);
936
937 default:
938 return qid.Error();
939 }
940 }
941 else
942 {
943 entryQid = *qid;
944 exists = true;
945 }
946 }
947
948 // Step 3: Create the item if it didn't exist and the user wants to create it.
949 if (!exists && WI_IsFlagSet(flags, OpenFlags::Create))
950 {
951 Expected<Qid> qid;
952
953 // This operation can create a directory if needed.
954 if (WI_IsFlagSet(flags, OpenFlags::Directory))
955 {
956 qid = newFile->MkDir(name, mode, gid);
957 }
958 else
959 {
960 // This will already open the file.
961 needOpen = false;
962 qid = newFile->Create(name, flags | OpenFlags::Exclusive, mode, gid);
963 }
964
965 if (!qid)
966 {
967 // If this is a non-exclusive create, we tried to find the item above and
968 // then tried to create it exclusively. There is the possibility of a race
969 // if the file got created in between the two calls, so retry if that
970 // happens.
971 //
972 // N.B. A non-exclusive create can't be used directly because the reply must
973 // indicate whether the file was created or opened.
974 if (qid.Error() == LX_EEXIST && !WI_IsFlagSet(flags, OpenFlags::Exclusive))
975 {
976 needOpen = true;
977 continue;
978 }
979
980 return qid.Error();
981 }
982
983 entryQid = *qid;
984 }
985
986 break;
987 }
988
989 // If a consistent result couldn't be reached, return an error.
990 if (retries == c_createRetryCount)
991 {
992 return LX_EIO;
993 }
994 }
995
996 // Step 4: Check the file type.
997 if (WI_IsFlagSet(wflags, WOpenFlags::NonDirectoryFile) && WI_IsFlagSet(entryQid.Type, QidType::Directory))
998 {
999 return LX_EISDIR;
1000 }
1001
1002 // Check for O_DIRECTORY too in case the open call is skipped below.
1003 if (WI_IsFlagSet(flags, OpenFlags::Directory) && !WI_IsFlagSet(entryQid.Type, QidType::Directory))
1004 {
1005 return LX_ENOTDIR;
1006 }
1007
1008 // Step 5: Check for delete access.
1009 if (WI_IsFlagSet(wflags, WOpenFlags::DeleteAccess))
1010 {
1011 auto result = newFile->Access(AccessFlags::Delete);
1012 if (result < 0)
1013 {
1014 return result;
1015 }
1016 }
1017
1018 // Step 6: Check how to handle leaf symlinks.
1019 if (WI_IsFlagSet(entryQid.Type, QidType::Symlink))
1020 {
1021 if (WI_IsFlagSet(wflags, WOpenFlags::OpenSymlink))
1022 {
1023 // No need to actually open, but do succeed.
1024 needOpen = false;
1025 }
1026 else
1027 {
1028 // Return a stopped status.
1029 return WriteWOpenReply(WOpenStatus::Stopped, nameCount, *newFile, attrMask, response);
1030 }
1031 }
1032
1033 // Step 7: Open if needed. This is only needed if:
1034 // - The file hasn't been opened already by a create; and
1035 // - Read/write access is requested to the file; or
1036 // - The open will have side effects (it will truncate the file).
1037 const auto access = (flags & OpenFlags::AccessMask);
1038 if (needOpen && (access != OpenFlags::NoAccess || WI_IsFlagSet(flags, OpenFlags::Truncate)))
1039 {
1040 // If the client specified O_NOACCESS, it means it doesn't want any access check done,
1041 // but O_NOACCESS actually checks for read/write, so fall back on read-only.
1042 // Also, directories can't be opened for write so change those to read-only too.
1043 if ((access == OpenFlags::NoAccess) ||
1044 (WI_IsFlagSet(entryQid.Type, QidType::Directory) && (access == OpenFlags::WriteOnly || access == OpenFlags::ReadWrite)))
1045 {
1046 flags = (flags & ~OpenFlags::AccessMask) | OpenFlags::ReadOnly;
1047 }
1048
1049 // Create would've been handled above; don't do it here.
1050 WI_ClearAllFlags(flags, OpenFlags::Create | OpenFlags::Exclusive);
1051 auto result = newFile->Open(flags);
1052 RETURN_ERROR_IF_UNEXPECTED(result);
1053 }
1054
1055 // Step 8: Get the attributes and reply
1056 const auto status = exists ? WOpenStatus::Opened : WOpenStatus::Created;
1057 auto result = WriteWOpenReply(status, nameCount, *newFile, attrMask, response);
1058 if (result < 0)
1059 {
1060 return result;
1061 }
1062
1063 EmplaceFid(newFid, newFile);
1064 return {};
1065 }
1066
1067 // Create a Rwopen message.
1068 LX_INT WriteWOpenReply(WOpenStatus status, UINT16 walked, Fid& fid, UINT64 mask, MessageResponse& response)
1069 {
1070 // Determine the attributes of the last entry found.
1071 auto stat = fid.GetAttr(mask);
1072 RETURN_ERROR_IF_UNEXPECTED(stat);
1073
1074 auto qid = std::get<Qid>(*stat);
1075 if (WI_IsFlagSet(qid.Type, QidType::Symlink))
1076 {
1077 response.EnsureSize(MessageType::Rwopen, LX_PATH_MAX, m_NegotiatedSize);
1078 }
1079 else
1080 {
1081 response.EnsureSize(MessageType::Rwopen, 0, m_NegotiatedSize);
1082 }
1083
1084 response.Writer.U8(static_cast<UINT8>(status));
1085 response.Writer.U16(walked);
1086 response.Writer.Qid(qid);
1087
1088 // If this is a symlink, get the target.
1089 if (WI_IsFlagSet(qid.Type, QidType::Symlink))
1090 {
1091 auto buffer = response.Writer.Peek().subspan(sizeof(UINT16));
1092 auto charSpan = gsl::span<char>(reinterpret_cast<char*>(buffer.data()), buffer.size());
1093 auto size = fid.ReadLink(charSpan);
1094 if (size)
1095 {
1096 response.Writer.U16(gsl::narrow_cast<UINT16>(*size));
1097 response.Writer.Next(*size);
1098 }
1099 else
1100 {
1101 response.Writer.U16(0);
1102 }
1103 }
1104 else
1105 {
1106 response.Writer.U16(0);
1107 }
1108
1109 response.Writer.U32(IoUnit());
1110 util::SpanWriteStatResult(response.Writer, std::get<StatResult>(*stat));
1111 response.Writer.U64(0); // btime sec (reserved)
1112 response.Writer.U64(0); // btime nsec (reserved)
1113 response.Writer.U64(0); // gen (reserved)
1114 response.Writer.U64(0); // data version (reserved)
1115 return {};
1116 }
1117
1118 // Cancel an outstanding request.
1119 Task<LX_INT> HandleFlush(SpanReader& reader)
1120 {
1121 const auto oldTag = reader.U16();
1122 std::unique_ptr<RequestInfo> waitRequest;
1123
1124 {
1125 std::lock_guard<std::mutex> lock{m_Requests->Lock};
1126
1127 // Search the list for the specified request.
1128 for (auto& request : m_Requests->Requests)
1129 {
1130 if (request.Tag == oldTag)
1131 {
1132 // A client should not send more than one Tflush on the same request. If it
1133 // does, another Tflush request already has ownership and it can't be taken
1134 // away. In this case, return success immediately and the client will have to
1135 // deal with the result of its broken behavior (but at least the server didn't
1136 // crash).
1137 if (!request.Cancelled)
1138 {
1139 // Mark the request cancelled and take ownership of it, so it can be
1140 // waited on outside the lock.
1141 // N.B. See the destructor of RequestTracker for why this is done this
1142 // way.
1143 request.Cancelled = true;
1144 waitRequest.reset(&request);
1145 break;
1146 }
1147 }
1148 }
1149 }
1150
1151 // Wait until the request completes before sending the Rflush response. This is necessary
1152 // because the server doesn't support true cancellation, and some messages may modify
1153 // server state (e.g. Twalk), so the client must receive the response to the real request
1154 // before it receives the Rflush response.
1155 if (waitRequest)
1156 {
1157 co_await waitRequest->Event;
1158 }
1159
1160 co_return 0;
1161 }
1162
1163 Task<bool> FillData(UINT32 requiredBytes, CancelToken& token)
1164 {
1165 WI_ASSERT(m_RequestData.size() < requiredBytes);
1166
1167 UINT32 validLength = static_cast<UINT32>(m_RequestData.size());
1168 if (validLength > 0 && m_RequestData.data() != m_RequestBuffer.data())
1169 {
1170 std::copy(m_RequestData.begin(), m_RequestData.end(), m_RequestBuffer.begin());
1171 }
1172
1173 while (validLength < requiredBytes)
1174 {
1175 size_t count = co_await m_Socket->RecvAsync(gsl::span<gsl::byte>(m_RequestBuffer).subspan(validLength), token);
1176 if (count == 0)
1177 {
1178 break;
1179 }
1180
1181 validLength += static_cast<int>(count);
1182 }
1183
1184 m_RequestData = gsl::span<gsl::byte>(m_RequestBuffer).subspan(0, validLength);
1185 co_return validLength >= requiredBytes;
1186 }
1187
1188 Task<gsl::span<gsl::byte>> NextMessage(CancelToken& token)
1189 {
1190 if (m_RequestData.size() < 4)
1191 {
1192 if (!co_await FillData(4, token))
1193 {
1194 co_return gsl::span<gsl::byte>{};
1195 }
1196 }
1197
1198 auto messageSize = SpanReader{m_RequestData}.U32();
1199 THROW_INVALID_IF(messageSize < 7 || messageSize > m_NegotiatedSize);
1200
1201 if (m_RequestData.size() < messageSize)
1202 {
1203 if (!co_await FillData(messageSize, token))
1204 {
1205 co_return gsl::span<gsl::byte>{};
1206 }
1207 }
1208
1209 auto message = m_RequestData.subspan(0, messageSize);
1210 m_RequestData = m_RequestData.subspan(messageSize);
1211 co_return message;
1212 }
1213
1214 // Process a message received from a socket.
1215 Task<void> ProcessMessage(gsl::span<const gsl::byte> message, CancelToken& sendToken)
1216 {
1217 SpanReader reader{message};
1218
1219 // Utilize a small stack-allocated buffer that's large enough for the largest response
1220 // without dynamic content (which is Rgetattr). Messages requiring a larger response will
1221 // allocate a dynamic buffer by calling MessageResponse::EnsureSize.
1222 // N.B. Message handlers that only return the header (e.g. HandleClunk) don't need to call
1223 // EnsureSize since the static buffer is always big enough for that.
1224 gsl::byte staticBuffer[c_staticBufferSize];
1225 MessageResponse response{staticBuffer};
1226 co_await ProcessMessage(reader, response);
1227 auto m = response.Writer.Result();
1228
1229 {
1230 auto lock = co_await m_SocketLock.Lock();
1231
1232 // Send the response.
1233 co_await m_Socket->SendAsync(m, sendToken);
1234 }
1235 }
1236
1237 // Process a Plan 9 message, and write the response to the specified buffer.
1238 Task<void> ProcessMessage(SpanReader& reader, MessageResponse& response)
1239 {
1240 LogMessage(reader.Span());
1241 reader.U32(); // message size, already validated
1242 auto messageType = reader.U8();
1243 const auto messageTag = reader.U16();
1244 const SpanWriter errorWriter{response.Writer};
1245
1246 LX_INT error;
1247 try
1248 {
1249 error = co_await HandleMessage(static_cast<MessageType>(messageType), reader, response);
1250 }
1251 catch (...)
1252 {
1253 LOG_CAUGHT_EXCEPTION();
1254 error = util::LinuxErrorFromCaughtException();
1255 }
1256
1257 if (error != 0)
1258 {
1259 response.Writer = errorWriter;
1260 response.Writer.U32(static_cast<UINT32>(-error));
1261 messageType = static_cast<UINT8>(MessageType::Tlerror);
1262 }
1263
1264 response.Writer.Header(static_cast<MessageType>(messageType + 1), messageTag);
1265 LogMessage(response.Writer.Result());
1266 }
1267
1268 // Process a message received from virtio.
1269 void ProcessMessageAsync(std::vector<gsl::byte>&& message, size_t responseSize, HandlerCallback&& callback) override
1270 {
1271 // Register the request so Tflush can wait on it if needed.
1272 const auto tag = SpanReader{gsl::make_span(message).subspan(TagOffset)}.U16();
1273 RequestTracker request{m_Requests, tag};
1274
1275 // Process the message in a coroutine. This routine will run synchronously until it hits
1276 // a suspension point (which it may or may not depending on the message). The coroutine
1277 // is not awaited here so if it does hit a suspension point the message will be completed
1278 // asynchronously.
1279 // N.B. The use of AsyncTask is required because the coroutine is not awaited.
1280 // N.B. Since this thread is not running the scheduler it will not be used to run other
1281 // coroutines if this coroutine hits a suspension point.
1282 RunAsyncTask(
1283 [this,
1284 localMessage = std::move(message),
1285 localRequest = std::move(request),
1286 responseSize,
1287 completionCallback = std::move(callback)]() mutable -> Task<void> {
1288 std::vector<gsl::byte> responseBuffer;
1289 try
1290 {
1291 SpanReader reader{localMessage};
1292
1293 // Since the response buffer was sized based on the virtio write span, it's not
1294 // allowed to reallocate it for a bigger response.
1295 responseBuffer.resize(responseSize);
1296 MessageResponse response{responseBuffer, false};
1297
1298 co_await ProcessMessage(reader, response);
1299 responseBuffer.resize(response.Writer.Size());
1300 }
1301 catch (...)
1302 {
1303 LOG_CAUGHT_EXCEPTION();
1304 responseBuffer.clear();
1305 }
1306
1307 completionCallback(responseBuffer);
1308 });
1309 }
1310
1311 public:
1312 Task<void> Run(CancelToken& parentToken) noexcept
1313 {
1314 Plan9TraceLoggingProvider::AcceptedConnection();
1315 CancelToken connectionToken(parentToken);
1316 CancelToken recvToken(connectionToken);
1317 CancelToken sendToken(connectionToken);
1318 constexpr size_t maximumMessages = 32; // maximum number of concurrent messages
1319 AsyncSemaphore messageSemaphore(maximumMessages);
1320 while (!connectionToken.Cancelled())
1321 {
1322 // Only a single read is performed at a time, so no locking is
1323 // necessary.
1324 gsl::span<gsl::byte> message{};
1325 try
1326 {
1327 message = co_await NextMessage(recvToken);
1328 }
1329 catch (...)
1330 {
1331 LOG_CAUGHT_EXCEPTION();
1332 }
1333
1334 if (message.empty())
1335 {
1336 break;
1337 }
1338
1339 // Register the request so Tflush can wait on it if needed.
1340 const auto tag = SpanReader{message.subspan(TagOffset)}.U16();
1341 RequestTracker request{m_Requests, tag};
1342 co_await messageSemaphore.Acquire(1);
1343
1344 // Process the message on a separate scheduled coroutine. Receiving
1345 // messages uses a shared buffer, which can be changed after the
1346 // next call to NextMessage, so make a copy of the message.
1347 RunScheduledTask(
1348 [this,
1349 releaseSemaphore = wil::scope_exit([&]() { messageSemaphore.Release(1); }),
1350 localMessage = std::vector<gsl::byte>{message.begin(), message.end()},
1351 localRequest = std::move(request),
1352 &connectionToken,
1353 &sendToken]() mutable -> Task<void> {
1354 try
1355 {
1356 co_await ProcessMessage(localMessage, sendToken);
1357 }
1358 catch (...)
1359 {
1360 LOG_CAUGHT_EXCEPTION();
1361 connectionToken.Cancel();
1362 }
1363 });
1364 }
1365
1366 // Wait until all messages are finished.
1367 connectionToken.Cancel();
1368 co_await messageSemaphore.Acquire(maximumMessages);
1369 Plan9TraceLoggingProvider::ConnectionDisconnected();
1370 co_return;
1371 }
1372
1373 private:
1374 std::shared_ptr<Fid> LookupFid(UINT32 fid)
1375 {
1376 std::shared_lock<std::shared_mutex> lock{m_FidsLock};
1377 const auto it = m_Fids.find(fid);
1378 THROW_UNEXPECTED_IF(it == m_Fids.end());
1379 return it->second;
1380 }
1381
1382 std::pair<std::shared_ptr<Fid>, std::shared_ptr<Fid>> LookupFidPair(UINT32 fid1, UINT32 fid2)
1383 {
1384 std::shared_lock<std::shared_mutex> lock{m_FidsLock};
1385 const auto it1 = m_Fids.find(fid1);
1386 THROW_UNEXPECTED_IF(it1 == m_Fids.end());
1387 const auto it2 = m_Fids.find(fid2);
1388 THROW_UNEXPECTED_IF(it2 == m_Fids.end());
1389 return {it1->second, it2->second};
1390 }
1391
1392 void EmplaceFid(UINT32 fid, std::shared_ptr<Fid> item)
1393 {
1394 std::lock_guard<std::shared_mutex> lock{m_FidsLock};
1395 const auto result = m_Fids.try_emplace(fid, item);
1396 THROW_INVALID_IF(!result.second);
1397 }
1398
1399 // Returns the maximum size of an IO request (0 for no limit).
1400 static UINT32 IoUnit()
1401 {
1402 return 0;
1403 }
1404
1405 static constexpr UINT32 MinimumRequestBufferSize = 4096;
1406 static constexpr UINT32 MaximumRequestBufferSize = 256 * 1024;
1407 static constexpr UINT32 InitialResponseBufferSize = 64;
1408
1409 AsyncLock m_SocketLock;
1410 ISocket* m_Socket{};
1411 std::shared_mutex m_FidsLock;
1412 std::map<UINT32, std::shared_ptr<Fid>> m_Fids;
1413 std::vector<gsl::byte> m_RequestBuffer{MaximumRequestBufferSize};
1414 gsl::span<gsl::byte> m_RequestData;
1415 std::shared_ptr<RequestList> m_Requests;
1416 UINT32 m_NegotiatedSize{InitialResponseBufferSize};
1417 bool m_Negotiated{false};
1418 bool m_AllowRenegotiate{false};
1419 bool m_Use9P2000W{false};
1420 IShareList& m_ShareList;
1421 };
1422
1423 AsyncTask HandleConnections(ISocket& listen, IShareList& shareList, CancelToken& token, WaitGroup& waitGroup)
1424 {
1425 std::atomic<size_t> connectionCount{};
1426
1427 try
1428 {
1429 while (!token.Cancelled())
1430 {
1431 Plan9TraceLoggingProvider::PreAccept();
1432 auto client = co_await listen.AcceptAsync(token);
1433 Plan9TraceLoggingProvider::PostAccept();
1434
1435 // If the operation was aborted, no socket is returned.
1436 if (!client)
1437 {
1438 Plan9TraceLoggingProvider::OperationAborted();
1439
1440 token.Cancel();
1441 break;
1442 }
1443
1444 if (connectionCount >= shareList.MaximumConnectionCount())
1445 {
1446 Plan9TraceLoggingProvider::TooManyConnections();
1447 // Terminate the client now so that there is quick feedback
1448 // that no more connections are allowed.
1449 client.reset();
1450 }
1451 else
1452 {
1453 ++connectionCount;
1454 Plan9TraceLoggingProvider::ClientConnected(connectionCount);
1455
1456 RunScheduledTask([client = std::move(client), keepAlive = waitGroup.Add(), &connectionCount, &shareList, &token]() -> Task<void> {
1457 auto decrementCount = wil::scope_exit([&]() {
1458 --connectionCount;
1459 Plan9TraceLoggingProvider::ClientDisconnected(connectionCount);
1460 });
1461 Handler handler{*client, shareList};
1462 co_await handler.Run(token);
1463 });
1464 }
1465 }
1466 }
1467 catch (...)
1468 {
1469 LOG_CAUGHT_EXCEPTION();
1470 token.Cancel();
1471 }
1472
1473 // Wait for the connection tasks to complete.
1474 co_await waitGroup.Wait();
1475
1476 WI_ASSERT(connectionCount == 0);
1477 }
1478
1479 // Creates a handler that can be used to process messages without a server socket, for use with
1480 // virtio servers.
1481 std::unique_ptr<IHandler> HandlerFactory::CreateHandler() const
1482 {
1483 // Since it's not possible to detect a "disconnect" with virtio, allow Tversion to be sent
1484 // multiple times so the device can be mounted/dismounted more than once without restarting
1485 // the VM.
1486 return std::make_unique<Handler>(m_shareList, true);
1487 }
1488
1489 } // namespace p9fs