Coverage Report

Created: 2025-07-01 06:18

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