Coverage Report

Created: 2026-11-12 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/WasmEdge/lib/host/wasi/vinode.cpp
Line
Count
Source
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright The WasmEdge Authors
3
4
#include "host/wasi/vinode.h"
5
#include "common/errcode.h"
6
#include "common/spdlog.h"
7
#include "host/wasi/environ.h"
8
#include "host/wasi/vfs.h"
9
#include <algorithm>
10
#include <cstddef>
11
#include <numeric>
12
#include <string>
13
14
using namespace std::literals;
15
16
namespace WasmEdge {
17
namespace Host {
18
namespace WASI {
19
20
namespace {
21
22
static inline constexpr const uint8_t kMaxNestedLinks = 8;
23
24
}
25
26
VINode::VINode(INode Node, __wasi_rights_t FRB, __wasi_rights_t FRI,
27
               std::string N)
28
0
    : Node(std::move(Node)), FsRightsBase(FRB), FsRightsInheriting(FRI),
29
0
      Name(std::move(N)) {}
30
31
std::shared_ptr<VINode> VINode::stdIn(__wasi_rights_t FRB,
32
0
                                      __wasi_rights_t FRI) {
33
0
  return std::make_shared<VINode>(INode::stdIn(), FRB, FRI);
34
0
}
35
36
std::shared_ptr<VINode> VINode::stdOut(__wasi_rights_t FRB,
37
0
                                       __wasi_rights_t FRI) {
38
0
  return std::make_shared<VINode>(INode::stdOut(), FRB, FRI);
39
0
}
40
41
std::shared_ptr<VINode> VINode::stdErr(__wasi_rights_t FRB,
42
0
                                       __wasi_rights_t FRI) {
43
0
  return std::make_shared<VINode>(INode::stdErr(), FRB, FRI);
44
0
}
45
46
WasiExpect<std::shared_ptr<VINode>>
47
0
VINode::fromFd(int32_t Fd, __wasi_rights_t FRB, __wasi_rights_t FRI) {
48
0
  auto NodeResult = INode::fromFd(Fd);
49
0
  if (!NodeResult) {
50
0
    return WasiUnexpect(NodeResult.error());
51
0
  }
52
0
  return std::make_shared<VINode>(std::move(*NodeResult), FRB, FRI);
53
0
}
54
0
std::string VINode::canonicalGuest(std::string_view Path) {
55
0
  std::vector<std::string_view> Parts;
56
57
0
  while (!Path.empty() && Path.front() == '/') {
58
0
    Path = Path.substr(1);
59
0
  }
60
0
  while (!Path.empty()) {
61
0
    auto Slash = Path.find('/');
62
0
    const auto Part = Path.substr(0, Slash);
63
0
    auto Remain = Path.substr(Part.size());
64
0
    while (!Remain.empty() && Remain.front() == '/') {
65
0
      Remain = Remain.substr(1);
66
0
    }
67
0
    if (Part.front() == '.' && Part.size() == 2 && Part[1] == '.') {
68
0
      if (!Parts.empty()) {
69
0
        Parts.pop_back();
70
0
      }
71
0
    } else if (Part.front() != '.' || Parts.size() != 1) {
72
0
      Parts.push_back(Part);
73
0
    }
74
0
    if (Remain.empty()) {
75
0
      break;
76
0
    }
77
0
    Path = Remain;
78
0
  }
79
0
  if (Parts.empty()) {
80
0
    Parts.push_back({});
81
0
  }
82
83
0
  std::string Result;
84
0
  Result.reserve(std::accumulate(
85
0
      Parts.begin(), Parts.end(), Parts.size(),
86
0
      [](size_t L, std::string_view P) { return L + P.size(); }));
87
0
  std::for_each(Parts.begin(), Parts.end(), [&Result](std::string_view P) {
88
0
    Result += P;
89
0
    Result += '/';
90
0
  });
91
0
  if (!Result.empty()) {
92
0
    Result.pop_back();
93
0
  }
94
95
0
  return Result;
96
0
}
97
98
WasiExpect<std::shared_ptr<VINode>> VINode::bind(__wasi_rights_t FRB,
99
                                                 __wasi_rights_t FRI,
100
                                                 std::string Name,
101
0
                                                 std::string SystemPath) {
102
0
  EXPECTED_TRY(auto Node,
103
0
               INode::open(std::move(SystemPath), __WASI_OFLAGS_DIRECTORY,
104
0
                           __wasi_fdflags_t(0), VFS::Read));
105
0
  return std::make_shared<VINode>(std::move(Node), FRB, FRI, std::move(Name));
106
0
}
107
108
WasiExpect<void> VINode::pathCreateDirectory(std::shared_ptr<VINode> Fd,
109
0
                                             std::string_view Path) {
110
0
  if (!Fd->can(__WASI_RIGHTS_PATH_CREATE_DIRECTORY)) {
111
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
112
0
  }
113
0
  EXPECTED_TRY(auto Buffer,
114
0
               resolvePath(Fd, Path, static_cast<__wasi_lookupflags_t>(0),
115
0
                           static_cast<VFS::Flags>(0), 0, false));
116
0
  return Fd->Node.pathCreateDirectory(std::string(Path));
117
0
}
118
119
WasiExpect<void> VINode::pathFilestatGet(std::shared_ptr<VINode> Fd,
120
                                         std::string_view Path,
121
                                         __wasi_lookupflags_t Flags,
122
0
                                         __wasi_filestat_t &Filestat) {
123
0
  if (!Fd->can(__WASI_RIGHTS_PATH_FILESTAT_GET)) {
124
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
125
0
  }
126
0
  EXPECTED_TRY(auto Buffer, resolvePath(Fd, Path, Flags));
127
0
  return Fd->Node.pathFilestatGet(std::string(Path), Filestat);
128
0
}
129
130
WasiExpect<void> VINode::pathFilestatSetTimes(std::shared_ptr<VINode> Fd,
131
                                              std::string_view Path,
132
                                              __wasi_lookupflags_t Flags,
133
                                              __wasi_timestamp_t ATim,
134
                                              __wasi_timestamp_t MTim,
135
0
                                              __wasi_fstflags_t FstFlags) {
136
0
  if (!Fd->can(__WASI_RIGHTS_PATH_FILESTAT_SET_TIMES)) {
137
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
138
0
  }
139
0
  EXPECTED_TRY(auto Buffer, resolvePath(Fd, Path, Flags));
140
0
  return Fd->Node.pathFilestatSetTimes(std::string(Path), ATim, MTim, FstFlags);
141
0
}
142
143
WasiExpect<void> VINode::pathLink(std::shared_ptr<VINode> Old,
144
                                  std::string_view OldPath,
145
                                  std::shared_ptr<VINode> New,
146
                                  std::string_view NewPath,
147
0
                                  __wasi_lookupflags_t LookupFlags) {
148
0
  if (unlikely(!New)) {
149
0
    return WasiUnexpect(__WASI_ERRNO_BADF);
150
0
  }
151
0
  if (!Old->can(__WASI_RIGHTS_PATH_LINK_SOURCE)) {
152
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
153
0
  }
154
0
  if (!New->can(__WASI_RIGHTS_PATH_LINK_TARGET)) {
155
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
156
0
  }
157
0
  if (LookupFlags & __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW) {
158
0
    return WasiUnexpect(__WASI_ERRNO_INVAL);
159
0
  }
160
0
  EXPECTED_TRY(auto OldBuffer,
161
0
               resolvePath(Old, OldPath, static_cast<__wasi_lookupflags_t>(0)));
162
0
  EXPECTED_TRY(auto NewBuffer,
163
0
               resolvePath(New, NewPath, static_cast<__wasi_lookupflags_t>(0)));
164
165
0
  return INode::pathLink(Old->Node, std::string(OldPath), New->Node,
166
0
                         std::string(NewPath));
167
0
}
168
169
WasiExpect<std::shared_ptr<VINode>>
170
VINode::pathOpen(std::shared_ptr<VINode> Fd, std::string_view Path,
171
                 __wasi_lookupflags_t LookupFlags, __wasi_oflags_t OpenFlags,
172
                 __wasi_rights_t FsRightsBase,
173
0
                 __wasi_rights_t FsRightsInheriting, __wasi_fdflags_t FdFlags) {
174
0
  if (OpenFlags & __WASI_OFLAGS_DIRECTORY) {
175
0
    FsRightsBase &= ~__WASI_RIGHTS_FD_SEEK;
176
0
  } else {
177
0
    FsRightsBase &= ~__WASI_RIGHTS_PATH_FILESTAT_GET;
178
0
    FsRightsInheriting &= ~__WASI_RIGHTS_PATH_FILESTAT_GET;
179
0
  }
180
181
0
  __wasi_rights_t RequiredRights = __WASI_RIGHTS_PATH_OPEN;
182
183
0
  if (OpenFlags & __WASI_OFLAGS_CREAT) {
184
0
    RequiredRights |= __WASI_RIGHTS_PATH_CREATE_FILE;
185
0
  }
186
0
  if (OpenFlags & __WASI_OFLAGS_TRUNC) {
187
0
    RequiredRights |= __WASI_RIGHTS_PATH_FILESTAT_SET_SIZE;
188
0
  }
189
190
0
  if (!Fd->can(RequiredRights)) {
191
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
192
0
  }
193
0
  const auto InheritingRights = imply(Fd->fsRightsInheriting());
194
0
  FsRightsBase &= InheritingRights;
195
0
  FsRightsInheriting &= InheritingRights;
196
0
  __wasi_rights_t RequiredFlagRights = static_cast<__wasi_rights_t>(0);
197
0
  if (FdFlags & __WASI_FDFLAGS_APPEND) {
198
0
    RequiredFlagRights |= __WASI_RIGHTS_FD_WRITE;
199
0
  }
200
0
  if (FdFlags & __WASI_FDFLAGS_DSYNC) {
201
0
    RequiredFlagRights |= __WASI_RIGHTS_FD_DATASYNC;
202
0
  }
203
0
  if (FdFlags & (__WASI_FDFLAGS_RSYNC | __WASI_FDFLAGS_SYNC)) {
204
0
    RequiredFlagRights |= __WASI_RIGHTS_FD_SYNC;
205
0
  }
206
0
  if ((InheritingRights & RequiredFlagRights) != RequiredFlagRights) {
207
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
208
0
  }
209
0
  const bool Read =
210
0
      (FsRightsBase & (__WASI_RIGHTS_FD_READ | __WASI_RIGHTS_FD_READDIR)) != 0;
211
0
  const bool Write =
212
0
      (FsRightsBase & (__WASI_RIGHTS_FD_WRITE | __WASI_RIGHTS_FD_ALLOCATE |
213
0
                       __WASI_RIGHTS_FD_FILESTAT_SET_SIZE)) != 0;
214
0
  EXPECTED_TRY(auto Buffer, resolvePath(Fd, Path, LookupFlags));
215
0
  VFS::Flags VFSFlags = static_cast<VFS::Flags>(0);
216
0
  if (Read) {
217
0
    VFSFlags |= VFS::Read;
218
0
  }
219
0
  if (Write) {
220
0
    VFSFlags |= VFS::Write;
221
0
  }
222
0
  return Fd->directOpen(Path, OpenFlags, FdFlags, VFSFlags, FsRightsBase,
223
0
                        FsRightsInheriting);
224
0
}
225
226
WasiExpect<void> VINode::pathReadlink(std::shared_ptr<VINode> Fd,
227
                                      std::string_view Path, Span<char> Buffer,
228
0
                                      __wasi_size_t &NRead) {
229
0
  if (!Fd->can(__WASI_RIGHTS_PATH_READLINK)) {
230
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
231
0
  }
232
0
  EXPECTED_TRY(auto PathBuffer,
233
0
               resolvePath(Fd, Path, static_cast<__wasi_lookupflags_t>(0)));
234
235
0
  return Fd->Node.pathReadlink(std::string(Path), Buffer, NRead);
236
0
}
237
238
WasiExpect<void> VINode::pathRemoveDirectory(std::shared_ptr<VINode> Fd,
239
0
                                             std::string_view Path) {
240
0
  if (!Fd->can(__WASI_RIGHTS_PATH_REMOVE_DIRECTORY)) {
241
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
242
0
  }
243
0
  EXPECTED_TRY(auto Buffer,
244
0
               resolvePath(Fd, Path, static_cast<__wasi_lookupflags_t>(0),
245
0
                           static_cast<VFS::Flags>(0), 0, false));
246
247
0
  return Fd->Node.pathRemoveDirectory(std::string(Path));
248
0
}
249
250
WasiExpect<void> VINode::pathRename(std::shared_ptr<VINode> Old,
251
                                    std::string_view OldPath,
252
                                    std::shared_ptr<VINode> New,
253
0
                                    std::string_view NewPath) {
254
0
  if (!Old->can(__WASI_RIGHTS_PATH_RENAME_SOURCE)) {
255
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
256
0
  }
257
0
  if (!New->can(__WASI_RIGHTS_PATH_RENAME_TARGET)) {
258
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
259
0
  }
260
0
  EXPECTED_TRY(auto OldBuffer,
261
0
               resolvePath(Old, OldPath, static_cast<__wasi_lookupflags_t>(0),
262
0
                           static_cast<VFS::Flags>(0), 0, false));
263
0
  EXPECTED_TRY(auto NewBuffer,
264
0
               resolvePath(New, NewPath, static_cast<__wasi_lookupflags_t>(0),
265
0
                           static_cast<VFS::Flags>(0), 0, false));
266
267
0
  return INode::pathRename(Old->Node, std::string(OldPath), New->Node,
268
0
                           std::string(NewPath));
269
0
}
270
271
WasiExpect<void> VINode::pathSymlink(std::string_view OldPath,
272
                                     std::shared_ptr<VINode> New,
273
0
                                     std::string_view NewPath) {
274
0
  if (unlikely(!New)) {
275
0
    return WasiUnexpect(__WASI_ERRNO_BADF);
276
0
  }
277
0
  if (!New->can(__WASI_RIGHTS_PATH_SYMLINK)) {
278
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
279
0
  }
280
  // Resolve the new path without following its trailing component (an existing
281
  // link must not be followed, so creating over it fails with EEXIST), and
282
  // capture the link directory's depth below the fd.
283
0
  uint32_t ParentDepth = 0;
284
0
  EXPECTED_TRY(auto NewBuffer,
285
0
               resolvePath(New, NewPath, static_cast<__wasi_lookupflags_t>(0),
286
0
                           static_cast<VFS::Flags>(0), 0, true, &ParentDepth));
287
288
  // Reject relative targets that resolve outside the preopen root; store
289
  // in-bounds targets as-is.
290
0
  if (isSymlinkTargetEscaping(OldPath, ParentDepth)) {
291
0
    return WasiUnexpect(__WASI_ERRNO_PERM);
292
0
  }
293
0
  return New->Node.pathSymlink(std::string(OldPath), std::string(NewPath));
294
0
}
295
296
WasiExpect<void> VINode::pathUnlinkFile(std::shared_ptr<VINode> Fd,
297
0
                                        std::string_view Path) {
298
0
  if (!Fd->can(__WASI_RIGHTS_PATH_UNLINK_FILE)) {
299
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
300
0
  }
301
0
  EXPECTED_TRY(auto Buffer,
302
0
               resolvePath(Fd, Path, static_cast<__wasi_lookupflags_t>(0)));
303
304
0
  return Fd->Node.pathUnlinkFile(std::string(Path));
305
0
}
306
307
WasiExpect<void>
308
VINode::getAddrinfo(std::string_view Node, std::string_view Service,
309
                    const __wasi_addrinfo_t &Hint, uint32_t MaxResLength,
310
                    Span<__wasi_addrinfo_t *> WasiAddrinfoArray,
311
                    Span<__wasi_sockaddr_t *> WasiSockaddrArray,
312
                    Span<Span<char>> AiAddrSaDataArray,
313
                    Span<Span<char>> AiCanonnameArray,
314
0
                    /*Out*/ __wasi_size_t &ResLength) noexcept {
315
0
  return INode::getAddrinfo(Node, Service, Hint, MaxResLength,
316
0
                            WasiAddrinfoArray, WasiSockaddrArray,
317
0
                            AiAddrSaDataArray, AiCanonnameArray, ResLength);
318
0
}
319
320
WasiExpect<std::shared_ptr<VINode>>
321
VINode::sockOpen(__wasi_address_family_t SysDomain,
322
0
                 __wasi_sock_type_t SockType) {
323
0
  EXPECTED_TRY(auto Node, INode::sockOpen(SysDomain, SockType));
324
0
  __wasi_rights_t Rights =
325
0
      __WASI_RIGHTS_SOCK_OPEN | __WASI_RIGHTS_SOCK_CLOSE |
326
0
      __WASI_RIGHTS_SOCK_RECV | __WASI_RIGHTS_SOCK_RECV_FROM |
327
0
      __WASI_RIGHTS_SOCK_SEND | __WASI_RIGHTS_SOCK_SEND_TO |
328
0
      __WASI_RIGHTS_SOCK_SHUTDOWN | __WASI_RIGHTS_SOCK_BIND |
329
0
      __WASI_RIGHTS_POLL_FD_READWRITE | __WASI_RIGHTS_FD_FDSTAT_SET_FLAGS |
330
0
      __WASI_RIGHTS_FD_READ | __WASI_RIGHTS_FD_WRITE;
331
0
  return std::make_shared<VINode>(std::move(Node), Rights, Rights);
332
0
}
333
334
WasiExpect<std::shared_ptr<VINode>>
335
0
VINode::sockAccept(__wasi_fdflags_t FdFlags) {
336
0
  EXPECTED_TRY(auto NewNode, Node.sockAccept(FdFlags));
337
0
  __wasi_rights_t Rights =
338
0
      __WASI_RIGHTS_SOCK_RECV | __WASI_RIGHTS_SOCK_RECV_FROM |
339
0
      __WASI_RIGHTS_SOCK_SEND | __WASI_RIGHTS_SOCK_SEND_TO |
340
0
      __WASI_RIGHTS_SOCK_SHUTDOWN | __WASI_RIGHTS_POLL_FD_READWRITE |
341
0
      __WASI_RIGHTS_FD_FDSTAT_SET_FLAGS | __WASI_RIGHTS_FD_READ |
342
0
      __WASI_RIGHTS_FD_WRITE;
343
0
  return std::make_shared<VINode>(std::move(NewNode), Rights, Rights,
344
0
                                  std::string());
345
0
}
346
347
WasiExpect<std::shared_ptr<VINode>>
348
VINode::directOpen(std::string_view Path, __wasi_oflags_t OpenFlags,
349
                   __wasi_fdflags_t FdFlags, VFS::Flags VFSFlags,
350
                   __wasi_rights_t RightsBase,
351
0
                   __wasi_rights_t RightsInheriting) {
352
0
  std::string PathStr(Path);
353
354
0
  EXPECTED_TRY(auto NewNode,
355
0
               Node.pathOpen(std::move(PathStr), OpenFlags, FdFlags, VFSFlags));
356
0
  return std::make_shared<VINode>(std::move(NewNode), RightsBase,
357
0
                                  RightsInheriting);
358
0
}
359
360
WasiExpect<std::vector<char>>
361
VINode::resolvePath(std::shared_ptr<VINode> &Fd, std::string_view &Path,
362
                    __wasi_lookupflags_t LookupFlags, VFS::Flags VFSFlags,
363
                    uint8_t LinkCount, bool FollowTrailingSlashes,
364
0
                    uint32_t *ResolvedDepth) {
365
0
  std::vector<std::shared_ptr<VINode>> PartFds;
366
0
  std::vector<char> Buffer;
367
0
  do {
368
    // check empty path
369
0
    if (Path.empty() && (VFSFlags & VFS::AllowEmpty) == 0) {
370
0
      return WasiUnexpect(__WASI_ERRNO_NOENT);
371
0
    }
372
373
    // check absolute path
374
0
    if (!Path.empty() && Path[0] == '/') {
375
0
      return WasiUnexpect(__WASI_ERRNO_PERM);
376
0
    }
377
378
0
    if (!Fd) {
379
0
      return WasiUnexpect(__WASI_ERRNO_BADF);
380
0
    }
381
382
0
    if (!Fd->isDirectory()) {
383
0
      return WasiUnexpect(__WASI_ERRNO_NOTDIR);
384
0
    }
385
386
0
    if (!Fd->canBrowse()) {
387
0
      return WasiUnexpect(__WASI_ERRNO_ACCES);
388
0
    }
389
390
0
    do {
391
      // check self type
392
0
      auto Slash = Path.find('/');
393
0
      const auto Part = Path.substr(0, Slash);
394
0
      auto Remain = Path.substr(Part.size());
395
0
      while (!Remain.empty() && Remain[0] == '/') {
396
0
        Remain = Remain.substr(1);
397
0
      }
398
0
      const bool LastPart = Remain.empty() && (!FollowTrailingSlashes ||
399
0
                                               Slash == std::string_view::npos);
400
401
0
      if (!Part.empty() && Part[0] == '.') {
402
0
        if (Part.size() == 1) {
403
0
          if (LastPart) {
404
0
            if (ResolvedDepth) {
405
0
              *ResolvedDepth = static_cast<uint32_t>(PartFds.size());
406
0
            }
407
0
            return Buffer;
408
0
          }
409
0
          Path = Remain;
410
0
          continue;
411
0
        }
412
0
        if (Part.size() == 2 && Part[1] == '.') {
413
0
          if (PartFds.empty()) {
414
0
            return WasiUnexpect(__WASI_ERRNO_PERM);
415
0
          }
416
0
          Fd = std::move(PartFds.back());
417
0
          PartFds.pop_back();
418
0
          Path = Remain;
419
0
          if (LastPart) {
420
0
            Path = "."sv;
421
0
            if (ResolvedDepth) {
422
0
              *ResolvedDepth = static_cast<uint32_t>(PartFds.size());
423
0
            }
424
0
            return Buffer;
425
0
          }
426
0
          continue;
427
0
        }
428
0
      }
429
430
0
      if (LastPart && !(LookupFlags & __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW)) {
431
0
        Path = Part;
432
0
        if (ResolvedDepth) {
433
0
          *ResolvedDepth = static_cast<uint32_t>(PartFds.size());
434
0
        }
435
0
        return Buffer;
436
0
      }
437
438
0
      __wasi_filestat_t Filestat{};
439
0
      if (auto Res = Fd->Node.pathFilestatGet(std::string(Part), Filestat);
440
0
          unlikely(!Res)) {
441
0
        if (LastPart) {
442
0
          Path = Part;
443
0
          if (ResolvedDepth) {
444
0
            *ResolvedDepth = static_cast<uint32_t>(PartFds.size());
445
0
          }
446
0
          return Buffer;
447
0
        }
448
0
        return WasiUnexpect(Res);
449
0
      }
450
451
0
      if (Filestat.filetype == __WASI_FILETYPE_SYMBOLIC_LINK) {
452
0
        if (++LinkCount >= kMaxNestedLinks) {
453
0
          return WasiUnexpect(__WASI_ERRNO_LOOP);
454
0
        }
455
456
0
        std::vector<char> NewBuffer(16384);
457
0
        __wasi_size_t NRead;
458
0
        EXPECTED_TRY(
459
0
            Fd->Node.pathReadlink(std::string(Part), NewBuffer, NRead));
460
0
        NewBuffer.resize(NRead);
461
        // Don't drop Buffer now because Path may referencing it.
462
0
        if (!Remain.empty()) {
463
0
          if (NewBuffer.back() != '/') {
464
0
            NewBuffer.push_back('/');
465
0
          }
466
0
          NewBuffer.insert(NewBuffer.end(), Remain.begin(), Remain.end());
467
0
        }
468
        // slow retry
469
0
        Buffer = std::move(NewBuffer);
470
0
        Path = std::string_view(Buffer.data(), Buffer.size());
471
0
        break;
472
0
      }
473
474
0
      if (LastPart) {
475
0
        Path = Part;
476
0
        if (ResolvedDepth) {
477
0
          *ResolvedDepth = static_cast<uint32_t>(PartFds.size());
478
0
        }
479
0
        return Buffer;
480
0
      }
481
482
0
      if (Filestat.filetype != __WASI_FILETYPE_DIRECTORY) {
483
0
        return WasiUnexpect(__WASI_ERRNO_NOTDIR);
484
0
      }
485
486
0
      EXPECTED_TRY(auto Child, Fd->Node.pathOpen(
487
0
                                   std::string(Part), __WASI_OFLAGS_DIRECTORY,
488
0
                                   static_cast<__wasi_fdflags_t>(0), VFSFlags));
489
      // fast retry
490
0
      PartFds.push_back(std::exchange(
491
0
          Fd, std::make_shared<VINode>(std::move(Child), Fd->FsRightsBase,
492
0
                                       Fd->FsRightsInheriting)));
493
0
      Path = Remain;
494
0
      if (Path.empty()) {
495
0
        Path = "."sv;
496
0
        if (ResolvedDepth) {
497
0
          *ResolvedDepth = static_cast<uint32_t>(PartFds.size());
498
0
        }
499
0
        return {};
500
0
      }
501
0
      continue;
502
0
    } while (true);
503
0
  } while (true);
504
0
}
505
506
} // namespace WASI
507
} // namespace Host
508
} // namespace WasmEdge