Coverage Report

Created: 2026-09-28 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/WasmEdge/lib/host/wasi/environ.cpp
Line
Count
Source
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright The WasmEdge Authors
3
4
#include "host/wasi/environ.h"
5
#include "common/errcode.h"
6
#include "common/spdlog.h"
7
#include "host/wasi/vfs.h"
8
#include "host/wasi/vinode.h"
9
10
using namespace std::literals;
11
12
namespace WasmEdge {
13
namespace Host {
14
namespace WASI {
15
16
namespace {
17
static inline constexpr const __wasi_rights_t kPreOpenBaseRightsReadOnly =
18
    __WASI_RIGHTS_PATH_OPEN | __WASI_RIGHTS_FD_READDIR |
19
    __WASI_RIGHTS_PATH_READLINK | __WASI_RIGHTS_PATH_FILESTAT_GET |
20
    __WASI_RIGHTS_FD_FILESTAT_GET;
21
static inline constexpr const __wasi_rights_t kPreOpenInheritingRightsReadOnly =
22
    __WASI_RIGHTS_FD_DATASYNC | __WASI_RIGHTS_FD_READ | __WASI_RIGHTS_FD_SEEK |
23
    __WASI_RIGHTS_FD_FDSTAT_SET_FLAGS | __WASI_RIGHTS_FD_SYNC |
24
    __WASI_RIGHTS_FD_TELL | __WASI_RIGHTS_FD_ADVISE | __WASI_RIGHTS_PATH_OPEN |
25
    __WASI_RIGHTS_FD_READDIR | __WASI_RIGHTS_PATH_READLINK |
26
    __WASI_RIGHTS_PATH_FILESTAT_GET | __WASI_RIGHTS_FD_FILESTAT_GET |
27
    __WASI_RIGHTS_POLL_FD_READWRITE;
28
static inline constexpr const __wasi_rights_t kPreOpenBaseRights =
29
    __WASI_RIGHTS_PATH_CREATE_DIRECTORY | __WASI_RIGHTS_PATH_CREATE_FILE |
30
    __WASI_RIGHTS_PATH_LINK_SOURCE | __WASI_RIGHTS_PATH_LINK_TARGET |
31
    __WASI_RIGHTS_PATH_OPEN | __WASI_RIGHTS_FD_READDIR |
32
    __WASI_RIGHTS_PATH_READLINK | __WASI_RIGHTS_PATH_RENAME_SOURCE |
33
    __WASI_RIGHTS_PATH_RENAME_TARGET | __WASI_RIGHTS_PATH_FILESTAT_GET |
34
    __WASI_RIGHTS_PATH_FILESTAT_SET_TIMES | __WASI_RIGHTS_FD_FILESTAT_GET |
35
    __WASI_RIGHTS_FD_FILESTAT_SET_TIMES | __WASI_RIGHTS_PATH_SYMLINK |
36
    __WASI_RIGHTS_PATH_REMOVE_DIRECTORY | __WASI_RIGHTS_PATH_UNLINK_FILE |
37
    __WASI_RIGHTS_PATH_FILESTAT_SET_SIZE;
38
static inline constexpr const __wasi_rights_t kPreOpenInheritingRights =
39
    __WASI_RIGHTS_FD_DATASYNC | __WASI_RIGHTS_FD_READ | __WASI_RIGHTS_FD_SEEK |
40
    __WASI_RIGHTS_FD_FDSTAT_SET_FLAGS | __WASI_RIGHTS_FD_SYNC |
41
    __WASI_RIGHTS_FD_TELL | __WASI_RIGHTS_FD_WRITE | __WASI_RIGHTS_FD_ADVISE |
42
    __WASI_RIGHTS_FD_ALLOCATE | __WASI_RIGHTS_PATH_CREATE_DIRECTORY |
43
    __WASI_RIGHTS_PATH_CREATE_FILE | __WASI_RIGHTS_PATH_LINK_SOURCE |
44
    __WASI_RIGHTS_PATH_LINK_TARGET | __WASI_RIGHTS_PATH_OPEN |
45
    __WASI_RIGHTS_FD_READDIR | __WASI_RIGHTS_PATH_READLINK |
46
    __WASI_RIGHTS_PATH_RENAME_SOURCE | __WASI_RIGHTS_PATH_RENAME_TARGET |
47
    __WASI_RIGHTS_PATH_FILESTAT_GET | __WASI_RIGHTS_PATH_FILESTAT_SET_SIZE |
48
    __WASI_RIGHTS_PATH_FILESTAT_SET_TIMES | __WASI_RIGHTS_FD_FILESTAT_GET |
49
    __WASI_RIGHTS_FD_FILESTAT_SET_SIZE | __WASI_RIGHTS_FD_FILESTAT_SET_TIMES |
50
    __WASI_RIGHTS_PATH_SYMLINK | __WASI_RIGHTS_PATH_REMOVE_DIRECTORY |
51
    __WASI_RIGHTS_PATH_UNLINK_FILE | __WASI_RIGHTS_POLL_FD_READWRITE |
52
    __WASI_RIGHTS_SOCK_SHUTDOWN;
53
static inline constexpr const __wasi_rights_t kStdInDefaultRights =
54
    __WASI_RIGHTS_FD_ADVISE | __WASI_RIGHTS_FD_FILESTAT_GET |
55
    __WASI_RIGHTS_FD_READ | __WASI_RIGHTS_POLL_FD_READWRITE;
56
static inline constexpr const __wasi_rights_t kStdOutDefaultRights =
57
    __WASI_RIGHTS_FD_ADVISE | __WASI_RIGHTS_FD_DATASYNC |
58
    __WASI_RIGHTS_FD_FILESTAT_GET | __WASI_RIGHTS_FD_SYNC |
59
    __WASI_RIGHTS_FD_WRITE | __WASI_RIGHTS_POLL_FD_READWRITE;
60
static inline constexpr const __wasi_rights_t kStdErrDefaultRights =
61
    kStdOutDefaultRights;
62
static inline constexpr const __wasi_rights_t kNoInheritingRights =
63
    static_cast<__wasi_rights_t>(0);
64
static inline constexpr const auto kReadOnly = "readonly"sv;
65
66
} // namespace
67
68
void Environ::init(Span<const std::string> Dirs, const std::string &ProgramName,
69
0
                   Span<const std::string> Args, Span<const std::string> Envs) {
70
0
  {
71
    // Open dir for WASI environment.
72
0
    std::vector<std::shared_ptr<VINode>> PreopenedDirs;
73
0
    PreopenedDirs.reserve(Dirs.size());
74
0
    for (const auto &Dir : Dirs) {
75
0
      const auto Pos = Dir.find(':');
76
0
      std::string HostDir =
77
0
          (Pos == std::string::npos) ? Dir : Dir.substr(Pos + 1);
78
      // Handle the readonly flag
79
0
      bool ReadOnly = false;
80
0
      if (const auto ROPos = HostDir.find(':'); ROPos != std::string::npos) {
81
0
        const auto Mode = HostDir.substr(ROPos + 1);
82
0
        HostDir = HostDir.substr(0, ROPos);
83
0
        if (kReadOnly == Mode) {
84
0
          ReadOnly = true;
85
0
        }
86
0
      }
87
0
      std::string GuestDir = VINode::canonicalGuest(
88
0
          (Pos == std::string::npos) ? std::string_view(Dir)
89
0
                                     : std::string_view(Dir).substr(0, Pos));
90
0
      if (GuestDir.size() == 0) {
91
0
        GuestDir = '/';
92
0
      }
93
0
      const auto BaseRights =
94
0
          ReadOnly ? kPreOpenBaseRightsReadOnly : kPreOpenBaseRights;
95
0
      const auto InheritingRights = ReadOnly ? kPreOpenInheritingRightsReadOnly
96
0
                                             : kPreOpenInheritingRights;
97
0
      if (auto Res = VINode::bind(BaseRights, InheritingRights,
98
0
                                  std::move(GuestDir), std::move(HostDir));
99
0
          unlikely(!Res)) {
100
0
        spdlog::error("Bind guest directory failed:{}"sv, Res.error());
101
0
        continue;
102
0
      } else {
103
0
        PreopenedDirs.emplace_back(std::move(*Res));
104
0
      }
105
0
    }
106
107
0
    std::sort(PreopenedDirs.begin(), PreopenedDirs.end());
108
109
0
    FdMap.emplace(0, VINode::stdIn(kStdInDefaultRights, kNoInheritingRights));
110
0
    FdMap.emplace(1, VINode::stdOut(kStdOutDefaultRights, kNoInheritingRights));
111
0
    FdMap.emplace(2, VINode::stdErr(kStdErrDefaultRights, kNoInheritingRights));
112
113
0
    int NewFd = 3;
114
0
    for (auto &PreopenedDir : PreopenedDirs) {
115
0
      FdMap.emplace(NewFd++, std::move(PreopenedDir));
116
0
    }
117
0
  }
118
119
0
  Arguments.resize(Args.size() + 1);
120
0
  Arguments.front() = ProgramName;
121
0
  std::copy(Args.begin(), Args.end(), Arguments.begin() + 1);
122
0
  Arguments.shrink_to_fit();
123
124
0
  EnvironVariables.resize(Envs.size());
125
0
  std::copy(Envs.begin(), Envs.end(), EnvironVariables.begin());
126
0
  EnvironVariables.shrink_to_fit();
127
128
0
  ExitCode = 0;
129
0
}
130
131
WasiExpect<void>
132
Environ::initWithFds(Span<const std::string> Dirs, std::string ProgramName,
133
                     Span<const std::string> Args, Span<const std::string> Envs,
134
0
                     int32_t StdInFd, int32_t StdOutFd, int32_t StdErrFd) {
135
0
  {
136
0
    auto StdInVNode =
137
0
        VINode::fromFd(StdInFd, kStdInDefaultRights, kNoInheritingRights);
138
0
    if (!StdInVNode) {
139
0
      return WasiUnexpect(StdInVNode.error());
140
0
    }
141
0
    FdMap.emplace(0, std::move(*StdInVNode));
142
0
    auto StdOutVNode =
143
0
        VINode::fromFd(StdOutFd, kStdOutDefaultRights, kNoInheritingRights);
144
0
    if (!StdOutVNode) {
145
0
      return WasiUnexpect(StdOutVNode.error());
146
0
    }
147
0
    FdMap.emplace(1, std::move(*StdOutVNode));
148
0
    auto StdErrVNode =
149
0
        VINode::fromFd(StdErrFd, kStdErrDefaultRights, kNoInheritingRights);
150
0
    if (!StdErrVNode) {
151
0
      return WasiUnexpect(StdErrVNode.error());
152
0
    }
153
0
    FdMap.emplace(2, std::move(*StdErrVNode));
154
155
    // Open dir for WASI environment.
156
0
    std::vector<std::shared_ptr<VINode>> PreopenedDirs;
157
0
    PreopenedDirs.reserve(Dirs.size());
158
0
    for (const auto &Dir : Dirs) {
159
0
      const auto Pos = Dir.find(':');
160
0
      std::string HostDir =
161
0
          (Pos == std::string::npos) ? Dir : Dir.substr(Pos + 1);
162
      // Handle the readonly flag
163
0
      bool ReadOnly = false;
164
0
      if (const auto ROPos = HostDir.find(':'); ROPos != std::string::npos) {
165
0
        const auto Mode = HostDir.substr(ROPos + 1);
166
0
        HostDir = HostDir.substr(0, ROPos);
167
0
        if (kReadOnly == Mode) {
168
0
          ReadOnly = true;
169
0
        }
170
0
      }
171
0
      std::string GuestDir = VINode::canonicalGuest(
172
0
          (Pos == std::string::npos) ? std::string_view(Dir)
173
0
                                     : std::string_view(Dir).substr(0, Pos));
174
0
      if (GuestDir.size() == 0) {
175
0
        GuestDir = '/';
176
0
      }
177
0
      const auto BaseRights =
178
0
          ReadOnly ? kPreOpenBaseRightsReadOnly : kPreOpenBaseRights;
179
0
      const auto InheritingRights = ReadOnly ? kPreOpenInheritingRightsReadOnly
180
0
                                             : kPreOpenInheritingRights;
181
0
      if (auto Res = VINode::bind(BaseRights, InheritingRights,
182
0
                                  std::move(GuestDir), std::move(HostDir));
183
0
          unlikely(!Res)) {
184
0
        spdlog::error("Bind guest directory failed:{}"sv, Res.error());
185
0
        continue;
186
0
      } else {
187
0
        PreopenedDirs.emplace_back(std::move(*Res));
188
0
      }
189
0
    }
190
191
0
    std::sort(PreopenedDirs.begin(), PreopenedDirs.end());
192
193
0
    int NewFd = 3;
194
0
    for (auto &PreopenedDir : PreopenedDirs) {
195
0
      FdMap.emplace(NewFd++, std::move(PreopenedDir));
196
0
    }
197
0
  }
198
199
0
  Arguments.resize(Args.size() + 1);
200
0
  Arguments.front() = std::move(ProgramName);
201
0
  std::copy(Args.begin(), Args.end(), Arguments.begin() + 1);
202
0
  Arguments.shrink_to_fit();
203
204
0
  EnvironVariables.resize(Envs.size());
205
0
  std::copy(Envs.begin(), Envs.end(), EnvironVariables.begin());
206
0
  EnvironVariables.shrink_to_fit();
207
208
0
  ExitCode = 0;
209
0
  return {};
210
0
}
211
212
0
void Environ::fini() noexcept {
213
0
  EnvironVariables.clear();
214
0
  Arguments.clear();
215
0
  FdMap.clear();
216
0
}
217
218
0
Environ::~Environ() noexcept { fini(); }
219
220
0
WasiExpect<bool> Environ::pathExists(std::string_view Path) const noexcept {
221
0
  __wasi_filestat_t Filestat;
222
  // Using the first default preopened FD 3 as the working directory.
223
0
  __wasi_fd_t BaseFd = 3;
224
0
  auto StatResult = const_cast<Environ *>(this)->pathFilestatGet(
225
0
      BaseFd, Path, static_cast<__wasi_lookupflags_t>(0), Filestat);
226
0
  if (StatResult) {
227
0
    return true;
228
0
  }
229
0
  if (StatResult.error() == __WASI_ERRNO_NOENT) {
230
0
    return false;
231
0
  }
232
0
  return WasiUnexpect(StatResult.error());
233
0
}
234
235
0
WasiExpect<bool> Environ::pathCanRead(std::string_view Path) const noexcept {
236
  // Using the first default preopened FD 3 as the working directory.
237
0
  __wasi_fd_t BaseFd = 3;
238
0
  auto Result = const_cast<Environ *>(this)->pathOpen(
239
0
      BaseFd, Path, static_cast<__wasi_lookupflags_t>(0),
240
0
      static_cast<__wasi_oflags_t>(0), __WASI_RIGHTS_FD_READ,
241
0
      __WASI_RIGHTS_FD_READ, static_cast<__wasi_fdflags_t>(0));
242
243
0
  if (Result) {
244
0
    const_cast<Environ *>(this)->fdClose(*Result);
245
0
    return true;
246
0
  }
247
0
  if (Result.error() == __WASI_ERRNO_ACCES ||
248
0
      Result.error() == __WASI_ERRNO_NOENT) {
249
0
    return false;
250
0
  }
251
0
  return WasiUnexpect(Result.error());
252
0
}
253
254
0
WasiExpect<bool> Environ::pathCanWrite(std::string_view Path) const noexcept {
255
  // Using the first default preopened FD 3 as the working directory.
256
0
  __wasi_fd_t BaseFd = 3;
257
0
  auto Result = const_cast<Environ *>(this)->pathOpen(
258
0
      BaseFd, Path, static_cast<__wasi_lookupflags_t>(0),
259
0
      static_cast<__wasi_oflags_t>(0), __WASI_RIGHTS_FD_WRITE,
260
0
      __WASI_RIGHTS_FD_WRITE, static_cast<__wasi_fdflags_t>(0));
261
262
0
  if (Result) {
263
0
    __wasi_fdstat_t FdStat{};
264
0
    auto StatResult = fdFdstatGet(*Result, FdStat);
265
0
    auto CloseResult = const_cast<Environ *>(this)->fdClose(*Result);
266
0
    if (!StatResult) {
267
0
      return WasiUnexpect(StatResult.error());
268
0
    }
269
0
    if (!CloseResult) {
270
0
      return WasiUnexpect(CloseResult.error());
271
0
    }
272
0
    return (EndianValue(FdStat.fs_rights_base).le() & __WASI_RIGHTS_FD_WRITE) !=
273
0
           0;
274
0
  }
275
0
  if (Result.error() == __WASI_ERRNO_ACCES ||
276
0
      Result.error() == __WASI_ERRNO_NOENT) {
277
0
    return false;
278
0
  }
279
0
  return WasiUnexpect(Result.error());
280
0
}
281
282
WasiExpect<__wasi_rights_t>
283
0
Environ::pathGetStats(std::string_view Path) const noexcept {
284
0
  __wasi_rights_t Rights = static_cast<__wasi_rights_t>(0);
285
0
  auto StatResult = pathExists(Path);
286
0
  if (StatResult.error()) {
287
0
    return WasiUnexpect(StatResult.error());
288
0
  }
289
0
  if (StatResult) {
290
    // File exists, so we can get file stats
291
0
    Rights |= __WASI_RIGHTS_PATH_FILESTAT_GET;
292
0
  }
293
0
  StatResult = pathCanRead(Path);
294
0
  if (StatResult.error()) {
295
0
    return WasiUnexpect(StatResult.error());
296
0
  }
297
0
  if (StatResult) {
298
0
    Rights |= __WASI_RIGHTS_FD_READ;
299
0
  }
300
0
  StatResult = pathCanWrite(Path);
301
0
  if (StatResult.error()) {
302
0
    return WasiUnexpect(StatResult.error());
303
0
  }
304
0
  if (StatResult) {
305
0
    Rights |= __WASI_RIGHTS_FD_WRITE;
306
0
  }
307
0
  return Rights;
308
0
}
309
310
} // namespace WASI
311
} // namespace Host
312
} // namespace WasmEdge