Coverage Report

Created: 2025-08-25 06:58

/src/WasmEdge/lib/host/wasi/vinode.cpp
Line
Count
Source (jump to first uncovered line)
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: 2019-2024 Second State INC
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, resolvePath(Fd, Path, false));
114
0
  return Fd->Node.pathCreateDirectory(std::string(Path));
115
0
}
116
117
WasiExpect<void> VINode::pathFilestatGet(std::shared_ptr<VINode> Fd,
118
                                         std::string_view Path,
119
                                         __wasi_lookupflags_t Flags,
120
0
                                         __wasi_filestat_t &Filestat) {
121
0
  if (!Fd->can(__WASI_RIGHTS_PATH_FILESTAT_GET)) {
122
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
123
0
  }
124
0
  EXPECTED_TRY(auto Buffer, resolvePath(Fd, Path, Flags));
125
0
  return Fd->Node.pathFilestatGet(std::string(Path), Filestat);
126
0
}
127
128
WasiExpect<void> VINode::pathFilestatSetTimes(std::shared_ptr<VINode> Fd,
129
                                              std::string_view Path,
130
                                              __wasi_lookupflags_t Flags,
131
                                              __wasi_timestamp_t ATim,
132
                                              __wasi_timestamp_t MTim,
133
0
                                              __wasi_fstflags_t FstFlags) {
134
0
  if (!Fd->can(__WASI_RIGHTS_PATH_FILESTAT_SET_TIMES)) {
135
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
136
0
  }
137
0
  EXPECTED_TRY(auto Buffer, resolvePath(Fd, Path, Flags));
138
0
  return Fd->Node.pathFilestatSetTimes(std::string(Path), ATim, MTim, FstFlags);
139
0
}
140
141
WasiExpect<void> VINode::pathLink(std::shared_ptr<VINode> Old,
142
                                  std::string_view OldPath,
143
                                  std::shared_ptr<VINode> New,
144
                                  std::string_view NewPath,
145
0
                                  __wasi_lookupflags_t LookupFlags) {
146
0
  if (unlikely(!New)) {
147
0
    return WasiUnexpect(__WASI_ERRNO_BADF);
148
0
  }
149
0
  if (!Old->can(__WASI_RIGHTS_PATH_LINK_SOURCE)) {
150
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
151
0
  }
152
0
  if (!New->can(__WASI_RIGHTS_PATH_LINK_TARGET)) {
153
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
154
0
  }
155
0
  EXPECTED_TRY(auto OldBuffer, resolvePath(Old, OldPath, LookupFlags));
156
0
  EXPECTED_TRY(auto NewBuffer, resolvePath(New, NewPath, LookupFlags));
157
158
0
  return INode::pathLink(Old->Node, std::string(OldPath), New->Node,
159
0
                         std::string(NewPath));
160
0
}
161
162
WasiExpect<std::shared_ptr<VINode>>
163
VINode::pathOpen(std::shared_ptr<VINode> Fd, std::string_view Path,
164
                 __wasi_lookupflags_t LookupFlags, __wasi_oflags_t OpenFlags,
165
                 __wasi_rights_t FsRightsBase,
166
0
                 __wasi_rights_t FsRightsInheriting, __wasi_fdflags_t FdFlags) {
167
0
  if (OpenFlags & __WASI_OFLAGS_DIRECTORY) {
168
0
    FsRightsBase &= ~__WASI_RIGHTS_FD_SEEK;
169
0
  } else {
170
0
    FsRightsBase &= ~__WASI_RIGHTS_PATH_FILESTAT_GET;
171
0
    FsRightsInheriting &= ~__WASI_RIGHTS_PATH_FILESTAT_GET;
172
0
  }
173
174
0
  __wasi_rights_t RequiredRights = __WASI_RIGHTS_PATH_OPEN;
175
0
  __wasi_rights_t RequiredInheritingRights = FsRightsBase | FsRightsInheriting;
176
0
  const bool Read =
177
0
      (FsRightsBase & (__WASI_RIGHTS_FD_READ | __WASI_RIGHTS_FD_READDIR)) != 0;
178
0
  const bool Write =
179
0
      (FsRightsBase &
180
0
       (__WASI_RIGHTS_FD_DATASYNC | __WASI_RIGHTS_FD_WRITE |
181
0
        __WASI_RIGHTS_FD_ALLOCATE | __WASI_RIGHTS_FD_FILESTAT_SET_SIZE)) != 0;
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
0
  if (FdFlags & __WASI_FDFLAGS_RSYNC) {
190
0
    RequiredInheritingRights |= __WASI_RIGHTS_FD_SYNC;
191
0
  }
192
0
  if (FdFlags & __WASI_FDFLAGS_DSYNC) {
193
0
    RequiredInheritingRights |= __WASI_RIGHTS_FD_DATASYNC;
194
0
  }
195
196
0
  if (!Fd->can(RequiredRights, RequiredInheritingRights)) {
197
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
198
0
  }
199
0
  EXPECTED_TRY(auto Buffer, resolvePath(Fd, Path, LookupFlags));
200
0
  VFS::Flags VFSFlags = static_cast<VFS::Flags>(0);
201
0
  if (Read) {
202
0
    VFSFlags |= VFS::Read;
203
0
  }
204
0
  if (Write) {
205
0
    VFSFlags |= VFS::Write;
206
0
  }
207
0
  return Fd->directOpen(Path, OpenFlags, FdFlags, VFSFlags, FsRightsBase,
208
0
                        FsRightsInheriting);
209
0
}
210
211
WasiExpect<void> VINode::pathReadlink(std::shared_ptr<VINode> Fd,
212
                                      std::string_view Path, Span<char> Buffer,
213
0
                                      __wasi_size_t &NRead) {
214
0
  if (!Fd->can(__WASI_RIGHTS_PATH_READLINK)) {
215
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
216
0
  }
217
0
  EXPECTED_TRY(auto PathBuffer,
218
0
               resolvePath(Fd, Path, static_cast<__wasi_lookupflags_t>(0)));
219
220
0
  return Fd->Node.pathReadlink(std::string(Path), Buffer, NRead);
221
0
}
222
223
WasiExpect<void> VINode::pathRemoveDirectory(std::shared_ptr<VINode> Fd,
224
0
                                             std::string_view Path) {
225
0
  if (!Fd->can(__WASI_RIGHTS_PATH_REMOVE_DIRECTORY)) {
226
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
227
0
  }
228
0
  EXPECTED_TRY(auto Buffer, resolvePath(Fd, Path, false));
229
230
0
  return Fd->Node.pathRemoveDirectory(std::string(Path));
231
0
}
232
233
WasiExpect<void> VINode::pathRename(std::shared_ptr<VINode> Old,
234
                                    std::string_view OldPath,
235
                                    std::shared_ptr<VINode> New,
236
0
                                    std::string_view NewPath) {
237
0
  if (!Old->can(__WASI_RIGHTS_PATH_RENAME_SOURCE)) {
238
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
239
0
  }
240
0
  if (!New->can(__WASI_RIGHTS_PATH_RENAME_TARGET)) {
241
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
242
0
  }
243
0
  EXPECTED_TRY(auto OldBuffer, resolvePath(Old, OldPath, false));
244
0
  EXPECTED_TRY(auto NewBuffer, resolvePath(New, NewPath, false));
245
246
0
  return INode::pathRename(Old->Node, std::string(OldPath), New->Node,
247
0
                           std::string(NewPath));
248
0
}
249
250
WasiExpect<void> VINode::pathSymlink(std::string_view OldPath,
251
                                     std::shared_ptr<VINode> New,
252
0
                                     std::string_view NewPath) {
253
0
  if (unlikely(!New)) {
254
0
    return WasiUnexpect(__WASI_ERRNO_BADF);
255
0
  }
256
0
  if (!New->can(__WASI_RIGHTS_PATH_SYMLINK)) {
257
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
258
0
  }
259
0
  EXPECTED_TRY(auto NewBuffer, resolvePath(New, NewPath));
260
261
0
  return New->Node.pathSymlink(std::string(OldPath), std::string(NewPath));
262
0
}
263
264
WasiExpect<void> VINode::pathUnlinkFile(std::shared_ptr<VINode> Fd,
265
0
                                        std::string_view Path) {
266
0
  if (!Fd->can(__WASI_RIGHTS_PATH_UNLINK_FILE)) {
267
0
    return WasiUnexpect(__WASI_ERRNO_NOTCAPABLE);
268
0
  }
269
0
  EXPECTED_TRY(auto Buffer,
270
0
               resolvePath(Fd, Path, static_cast<__wasi_lookupflags_t>(0)));
271
272
0
  return Fd->Node.pathUnlinkFile(std::string(Path));
273
0
}
274
275
WasiExpect<void>
276
VINode::getAddrinfo(std::string_view Node, std::string_view Service,
277
                    const __wasi_addrinfo_t &Hint, uint32_t MaxResLength,
278
                    Span<__wasi_addrinfo_t *> WasiAddrinfoArray,
279
                    Span<__wasi_sockaddr_t *> WasiSockaddrArray,
280
                    Span<char *> AiAddrSaDataArray,
281
                    Span<char *> AiCanonnameArray,
282
0
                    /*Out*/ __wasi_size_t &ResLength) noexcept {
283
0
  return INode::getAddrinfo(Node, Service, Hint, MaxResLength,
284
0
                            WasiAddrinfoArray, WasiSockaddrArray,
285
0
                            AiAddrSaDataArray, AiCanonnameArray, ResLength);
286
0
}
287
288
WasiExpect<std::shared_ptr<VINode>>
289
VINode::sockOpen(__wasi_address_family_t SysDomain,
290
0
                 __wasi_sock_type_t SockType) {
291
0
  EXPECTED_TRY(auto Node, INode::sockOpen(SysDomain, SockType));
292
0
  __wasi_rights_t Rights =
293
0
      __WASI_RIGHTS_SOCK_OPEN | __WASI_RIGHTS_SOCK_CLOSE |
294
0
      __WASI_RIGHTS_SOCK_RECV | __WASI_RIGHTS_SOCK_RECV_FROM |
295
0
      __WASI_RIGHTS_SOCK_SEND | __WASI_RIGHTS_SOCK_SEND_TO |
296
0
      __WASI_RIGHTS_SOCK_SHUTDOWN | __WASI_RIGHTS_SOCK_BIND |
297
0
      __WASI_RIGHTS_POLL_FD_READWRITE | __WASI_RIGHTS_FD_FDSTAT_SET_FLAGS |
298
0
      __WASI_RIGHTS_FD_READ | __WASI_RIGHTS_FD_WRITE;
299
0
  return std::make_shared<VINode>(std::move(Node), Rights, Rights);
300
0
}
301
302
WasiExpect<std::shared_ptr<VINode>>
303
0
VINode::sockAccept(__wasi_fdflags_t FdFlags) {
304
0
  EXPECTED_TRY(auto NewNode, Node.sockAccept(FdFlags));
305
0
  __wasi_rights_t Rights =
306
0
      __WASI_RIGHTS_SOCK_RECV | __WASI_RIGHTS_SOCK_RECV_FROM |
307
0
      __WASI_RIGHTS_SOCK_SEND | __WASI_RIGHTS_SOCK_SEND_TO |
308
0
      __WASI_RIGHTS_SOCK_SHUTDOWN | __WASI_RIGHTS_POLL_FD_READWRITE |
309
0
      __WASI_RIGHTS_FD_FDSTAT_SET_FLAGS | __WASI_RIGHTS_FD_READ |
310
0
      __WASI_RIGHTS_FD_WRITE;
311
0
  return std::make_shared<VINode>(std::move(NewNode), Rights, Rights,
312
0
                                  std::string());
313
0
}
314
315
WasiExpect<std::shared_ptr<VINode>>
316
VINode::directOpen(std::string_view Path, __wasi_oflags_t OpenFlags,
317
                   __wasi_fdflags_t FdFlags, VFS::Flags VFSFlags,
318
                   __wasi_rights_t RightsBase,
319
0
                   __wasi_rights_t RightsInheriting) {
320
0
  std::string PathStr(Path);
321
322
0
  EXPECTED_TRY(auto NewNode,
323
0
               Node.pathOpen(std::move(PathStr), OpenFlags, FdFlags, VFSFlags));
324
0
  return std::make_shared<VINode>(std::move(NewNode), RightsBase,
325
0
                                  RightsInheriting);
326
0
}
327
328
WasiExpect<std::vector<char>>
329
VINode::resolvePath(std::shared_ptr<VINode> &Fd, std::string_view &Path,
330
                    __wasi_lookupflags_t LookupFlags, VFS::Flags VFSFlags,
331
0
                    uint8_t LinkCount, bool FollowTrailingSlashes) {
332
0
  std::vector<std::shared_ptr<VINode>> PartFds;
333
0
  std::vector<char> Buffer;
334
0
  do {
335
    // check empty path
336
0
    if (Path.empty() && (VFSFlags & VFS::AllowEmpty) == 0) {
337
0
      return WasiUnexpect(__WASI_ERRNO_NOENT);
338
0
    }
339
340
    // check absolute path
341
0
    if (!Path.empty() && Path[0] == '/') {
342
0
      return WasiUnexpect(__WASI_ERRNO_PERM);
343
0
    }
344
345
0
    if (!Fd) {
346
0
      return WasiUnexpect(__WASI_ERRNO_BADF);
347
0
    }
348
349
0
    if (!Fd->isDirectory()) {
350
0
      return WasiUnexpect(__WASI_ERRNO_NOTDIR);
351
0
    }
352
353
0
    if (!Fd->canBrowse()) {
354
0
      return WasiUnexpect(__WASI_ERRNO_ACCES);
355
0
    }
356
357
0
    do {
358
      // check self type
359
0
      auto Slash = Path.find('/');
360
0
      const auto Part = Path.substr(0, Slash);
361
0
      auto Remain = Path.substr(Part.size());
362
0
      while (!Remain.empty() && Remain[0] == '/') {
363
0
        Remain = Remain.substr(1);
364
0
      }
365
0
      const bool LastPart = Remain.empty() && (!FollowTrailingSlashes ||
366
0
                                               Slash == std::string_view::npos);
367
368
0
      if (!Part.empty() && Part[0] == '.') {
369
0
        if (Part.size() == 1) {
370
0
          if (LastPart) {
371
0
            return Buffer;
372
0
          }
373
0
          Path = Remain;
374
0
          continue;
375
0
        }
376
0
        if (Part.size() == 2 && Part[1] == '.') {
377
0
          if (PartFds.empty()) {
378
0
            return WasiUnexpect(__WASI_ERRNO_PERM);
379
0
          }
380
0
          Fd = std::move(PartFds.back());
381
0
          PartFds.pop_back();
382
0
          Path = Remain;
383
0
          if (LastPart) {
384
0
            Path = "."sv;
385
0
            return Buffer;
386
0
          }
387
0
          continue;
388
0
        }
389
0
      }
390
391
0
      if (LastPart && !(LookupFlags & __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW)) {
392
0
        Path = Part;
393
0
        return Buffer;
394
0
      }
395
396
0
      __wasi_filestat_t Filestat;
397
0
      if (auto Res = Fd->Node.pathFilestatGet(std::string(Part), Filestat);
398
0
          unlikely(!Res)) {
399
0
        if (LastPart) {
400
0
          Path = Part;
401
0
          return Buffer;
402
0
        }
403
0
        return WasiUnexpect(Res);
404
0
      }
405
406
0
      if (Filestat.filetype == __WASI_FILETYPE_SYMBOLIC_LINK) {
407
0
        if (++LinkCount >= kMaxNestedLinks) {
408
0
          return WasiUnexpect(__WASI_ERRNO_LOOP);
409
0
        }
410
411
0
        std::vector<char> NewBuffer(16384);
412
0
        __wasi_size_t NRead;
413
0
        EXPECTED_TRY(
414
0
            Fd->Node.pathReadlink(std::string(Part), NewBuffer, NRead));
415
0
        NewBuffer.resize(NRead);
416
        // Don't drop Buffer now because Path may referencing it.
417
0
        if (!Remain.empty()) {
418
0
          if (NewBuffer.back() != '/') {
419
0
            NewBuffer.push_back('/');
420
0
          }
421
0
          NewBuffer.insert(NewBuffer.end(), Remain.begin(), Remain.end());
422
0
        }
423
        // slow retry
424
0
        Buffer = std::move(NewBuffer);
425
0
        Path = std::string_view(Buffer.data(), Buffer.size());
426
0
        break;
427
0
      }
428
429
0
      if (LastPart) {
430
0
        Path = Part;
431
0
        return Buffer;
432
0
      }
433
434
0
      if (Filestat.filetype != __WASI_FILETYPE_DIRECTORY) {
435
0
        return WasiUnexpect(__WASI_ERRNO_NOTDIR);
436
0
      }
437
438
0
      EXPECTED_TRY(auto Child, Fd->Node.pathOpen(
439
0
                                   std::string(Part), __WASI_OFLAGS_DIRECTORY,
440
0
                                   static_cast<__wasi_fdflags_t>(0), VFSFlags));
441
      // fast retry
442
0
      PartFds.push_back(std::exchange(
443
0
          Fd, std::make_shared<VINode>(std::move(Child), Fd->FsRightsBase,
444
0
                                       Fd->FsRightsInheriting)));
445
0
      Path = Remain;
446
0
      if (Path.empty()) {
447
0
        Path = "."sv;
448
0
        return {};
449
0
      }
450
0
      continue;
451
0
    } while (true);
452
0
  } while (true);
453
0
}
454
455
} // namespace WASI
456
} // namespace Host
457
} // namespace WasmEdge