AsmGrader 0.0.0
Loading...
Searching...
No Matches
linux.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <fmt/format.h>
8#include <fmt/ostream.h>
9#include <libassert/assert.hpp>
10#include <range/v3/algorithm/transform.hpp>
11
12#include <cerrno>
13#include <csignal>
14#include <cstddef>
15#include <cstdio>
16#include <cstdlib>
17#include <ctime>
18#include <optional>
19#include <string>
20#include <system_error>
21#include <vector>
22
23#include <bits/types/siginfo_t.h>
24#include <fcntl.h>
25#include <sched.h>
26#include <signal.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <sys/ioctl.h>
31#include <sys/ptrace.h>
32#include <sys/stat.h>
33#include <sys/types.h>
34#include <sys/wait.h>
35#include <unistd.h>
36
38
39inline std::error_code make_error_code(int err = errno) {
40 return {err, std::generic_category()};
41}
42
45inline Expected<ssize_t> write(int fd, const std::string& data) {
46 ssize_t res = ::write(fd, data.data(), data.size());
47
48 if (res == -1) {
49 auto err = make_error_code(errno);
50
51 LOG_DEBUG("write failed: '{}'", err);
52 return err;
53 }
54
55 return res;
56}
57
60inline Expected<std::string> read(int fd, size_t count) { // NOLINT
61 std::string buffer(count, '\0');
62
63 ssize_t res = ::read(fd, buffer.data(), count);
64
65 if (res == -1) {
66 auto err = make_error_code(errno);
67
68 LOG_DEBUG("read failed: '{}'", err);
69 return err;
70 }
71
72 DEBUG_ASSERT(res >= 0, "read result is negative and != -1");
73 buffer.resize(static_cast<std::size_t>(res));
74
75 return buffer;
76}
77
80inline Expected<> close(int fd) {
81 int res = ::close(fd);
82
83 if (res == -1) {
84 auto err = make_error_code(errno);
85
86 LOG_DEBUG("close failed: '{}'", err);
87 return err;
88 }
89
90 return {};
91}
92
95inline Expected<> kill(pid_t pid, int sig) {
96 int res = ::kill(pid, sig);
97
98 if (res == -1) {
99 auto err = make_error_code(errno);
100
101 LOG_DEBUG("kill failed: '{}'", err);
102 return err;
103 }
104
105 return {};
106}
107
111inline Expected<> execve(const std::string& exec, const std::vector<std::string>& args,
112 const std::vector<std::string>& envp) {
113 // Reason: execve requires non-const strings
114 // NOLINTBEGIN(cppcoreguidelines-pro-type-const-cast)
115 std::vector<char*> cstr_arg_list(args.size() + 2, nullptr);
116 std::vector<char*> cstr_envp_list(envp.size() + 1, nullptr);
117
118 auto to_cstr = [](const std::string& str) { return const_cast<char*>(str.c_str()); };
119
120 cstr_arg_list.front() = const_cast<char*>(exec.c_str());
121 ranges::transform(args, cstr_arg_list.begin() + 1, to_cstr);
122 ranges::transform(envp, cstr_envp_list.begin(), to_cstr);
123
124 // NOLINTEND(cppcoreguidelines-pro-type-const-cast)
125
126 int res = ::execve(exec.data(), cstr_arg_list.data(), cstr_envp_list.data());
127
128 auto err = make_error_code(errno);
129
130 if (res == -1) {
131 LOG_DEBUG("execve failed: '{}'", err);
132 } else {
133 LOG_DEBUG("execve failed (INVALID RETURN CODE = {}): '{}'", res, err);
134 }
135
136 return err;
137}
138
139struct Fork
140{
141 enum { Parent, Child } which;
142
143 pid_t pid; // Only valid if type == child
144};
145
149 int res = ::fork();
150
151 if (res == -1) {
152 auto err = make_error_code(errno);
153 LOG_DEBUG("fork failed: '{}'", err);
154 return err;
155 }
156
157 if (res == 0) {
158 return Fork{.which = Fork::Child, .pid = 0};
159 }
160
161 return Fork{.which = Fork::Parent, .pid = res};
162}
163
166inline Expected<int> open(const std::string& pathname, int flags, mode_t mode = 0) {
167 // NOLINTNEXTLINE(*vararg)
168 int res = ::open(pathname.c_str(), flags, mode);
169
170 if (res == -1) {
171 auto err = make_error_code(errno);
172 LOG_DEBUG("open failed: '{}'", err);
173 return err;
174 }
175
176 return res;
177}
178
181inline Expected<off_t> lseek(int fd, off_t offset, int whence) {
182 off_t res = ::lseek(fd, offset, whence);
183
184 if (res == -1) {
185 auto err = make_error_code(errno);
186 LOG_DEBUG("lseek failed: '{}'", err);
187 return err;
188 }
189
190 return res;
191}
192
195inline Expected<> dup2(int oldfd, int newfd) {
196 int res = ::dup2(oldfd, newfd);
197
198 if (res != newfd) {
199 auto err = make_error_code(errno);
200
201 if (res == -1) {
202 LOG_DEBUG("dup2 failed: '{}'", err);
203 } else {
204 LOG_DEBUG("dup2 failed (INVALID RETURN CODE = {}): '{}'", res, err);
205 }
206
207 return err;
208 }
209
210 return {};
211}
212
215// NOLINTNEXTLINE(google-runtime-int)
216inline Expected<int> ioctl(int fd, unsigned long request, void* argp) {
217 // NOLINTNEXTLINE(*vararg)
218 int res = ::ioctl(fd, request, argp);
219
220 if (res == -1) {
221 auto err = make_error_code(errno);
222
223 LOG_DEBUG("ioctl failed: '{}'", err);
224
225 return err;
226 }
227
228 return res;
229}
230
233inline Expected<int> fcntl(int fd, int cmd, std::optional<int> arg = std::nullopt) {
234 int res{};
235
236 if (arg) {
237 // NOLINTNEXTLINE(*vararg)
238 res = ::fcntl(fd, cmd, arg.value());
239 } else {
240 // NOLINTNEXTLINE(*vararg)
241 res = ::fcntl(fd, cmd);
242 }
243
244 if (res == -1) {
245 auto err = make_error_code(errno);
246
247 LOG_DEBUG("fcntl failed: '{}'", err);
248
249 return err;
250 }
251
252 return Expected<int>{res};
253}
254
257inline Expected<siginfo_t> waitid(idtype_t idtype, id_t id, int options = WSTOPPED | WEXITED) {
258 siginfo_t info;
259 int res = ::waitid(idtype, id, &info, options);
260
261 if (res == -1) {
262 auto err = make_error_code(errno);
263
264 LOG_DEBUG("waitid failed: '{}'", err);
265
266 return err;
267 }
268
269 return info;
270}
271
274inline Expected<> raise(int sig) {
275 int res = ::raise(sig);
276
277 if (res == -1) {
278 auto err = std::error_code(errno, std::system_category());
279
280 LOG_DEBUG("raise failed: '{}'", err);
281
282 return err;
283 }
284
285 return {};
286}
287
288struct Pipe
289{
292};
293
294// Ensure that fds are packed so that pipe works properly
295static_assert(offsetof(Pipe, read_fd) + sizeof(Pipe::read_fd) == offsetof(Pipe, write_fd));
296
299inline Expected<Pipe> pipe2(int flags = 0) {
300 Pipe pipe{};
301
302 int res = ::pipe2(&pipe.read_fd, flags);
303
304 if (res == -1) {
305 auto err = make_error_code(errno);
306
307 LOG_DEBUG("pipe failed: '{}'", err);
308
309 return err;
310 }
311
312 return pipe;
313}
314
317// NOLINTBEGIN(google-runtime-int)
319template <typename AddrT = void*, typename DataT = void*>
321 requires(sizeof(AddrT) <= sizeof(void*) && sizeof(DataT) <= sizeof(void*))
322inline Expected<long> ptrace(int request, pid_t pid = 0, AddrT addr = NULL, DataT data = NULL) {
323 // clear errno before calling
324 errno = 0;
325
326 // Reasoning
327 // google-runtime-int : `long` is based on ptrace(2) spec
328 // cppcoreguidelines-pro-type-vararg : this is a wrapper for ptrace
329 // reinterpret-cast : ptrace spec is `void*`, caller of this wrapper should not care
330 // NOLINTBEGIN(cppcoreguidelines-pro-type-vararg,cppcoreguidelines-pro-type-reinterpret-cast)
331 long res = ::ptrace(static_cast<enum __ptrace_request>(request), pid, reinterpret_cast<void*>(addr),
332 reinterpret_cast<void*>(data));
333 // NOLINTEND(cppcoreguidelines-pro-type-vararg,cppcoreguidelines-pro-type-reinterpret-cast)
334 // NOLINTEND(google-runtime-int)
335
336 // see the Return section of ptrace(2)
337 if (errno) {
338 auto err = make_error_code(errno);
339
340 LOG_DEBUG("ptrace(req={}, pid={}, addr={}, data={}) failed: '{}'", request, pid, fmt_or_unknown(addr),
341 fmt_or_unknown(data), err);
342
343 return err;
344 }
345
346 return res;
347}
348
350inline Expected<struct ::stat> stat(const std::string& pathname) {
351 struct ::stat data_result{};
352
353 int res = ::stat(pathname.c_str(), &data_result);
354
355 if (res == -1) {
356 auto err = make_error_code(errno);
357
358 LOG_DEBUG("stat failed: '{}'", err);
359
360 return err;
361 }
362
363 return data_result;
364}
365
370 return ::getpid();
371}
372
374 return ::getppid();
375}
376
379{
380public:
381 // TODO: Consider refactor with explicit ctor and conversion
382
383 // NOLINTNEXTLINE(google-explicit-constructor)
384 Signal(int signal_num)
385 : signal_num_{signal_num} {};
386
387 // NOLINTNEXTLINE(google-explicit-constructor)
388 operator int() const { return signal_num_; }
389
390 std::string to_string() const { return sigdescr_np(signal_num_); }
391
392 friend std::string format_as(const Signal& from) { return from.to_string(); }
393
394private:
395 int signal_num_;
396};
397
398// TODO: Switch to using sigaction
399using SignalHandlerT = void (*)(int);
400
402 SignalHandlerT prev_handler = ::signal(sig, handler);
403
404 if (prev_handler == SIG_ERR) {
405 auto err = make_error_code();
406
407 LOG_DEBUG("signal failed: '{}'", err);
408
409 return err;
410 }
411
412 return prev_handler;
413}
414
415} // namespace asmgrader::linux
std::variant wrapper for a partial implementation of C++23's expected type
Definition expected.hpp:34
Value type to behave as a linux signal.
Definition linux.hpp:379
friend std::string format_as(const Signal &from)
Definition linux.hpp:392
std::string to_string() const
Definition linux.hpp:390
Signal(int signal_num)
Definition linux.hpp:384
#define LOG_DEBUG(...)
Definition logging.hpp:44
Definition linux.hpp:37
Expected< Fork > fork()
see dup(2) and ForkExpected returns result from enum; logs failure at debug level
Definition linux.hpp:148
Expected< Pipe > pipe2(int flags=0)
see pipe2(2) returns success/failure; logs failure at debug level
Definition linux.hpp:299
Expected raise(int sig)
see raise(3) returns success/failure; logs failure at debug level
Definition linux.hpp:274
Expected< off_t > lseek(int fd, off_t offset, int whence)
see lseek(2) returns success/failure; logs failure at debug level
Definition linux.hpp:181
Expected< pid_t > getppid()
Definition linux.hpp:373
void(*)(int) SignalHandlerT
Definition linux.hpp:399
Expected< ssize_t > write(int fd, const std::string &data)
writes to a file descriptor. See write(2) returns success/failure; logs failure at debug level
Definition linux.hpp:45
Expected dup2(int oldfd, int newfd)
see dup(2) returns success/failure; logs failure at debug level
Definition linux.hpp:195
Expected< siginfo_t > waitid(idtype_t idtype, id_t id, int options=WSTOPPED|WEXITED)
see waitid(2) returns success/failure; logs failure at debug level
Definition linux.hpp:257
Expected< int > open(const std::string &pathname, int flags, mode_t mode=0)
see open(2) returns success/failure; logs failure at debug level
Definition linux.hpp:166
Expected< pid_t > getpid()
see getpid(2) and getppid(2) these functions "cannot fail" according to the manpage....
Definition linux.hpp:369
Expected< std::string > read(int fd, size_t count)
reads fromm a file descriptor. See read(2) returns success/failure; logs failure at debug level
Definition linux.hpp:60
Expected< int > fcntl(int fd, int cmd, std::optional< int > arg=std::nullopt)
see fcntl(2) returns success/failure; logs failure at debug level
Definition linux.hpp:233
std::error_code make_error_code(int err=errno)
Definition linux.hpp:39
Expected< int > ioctl(int fd, unsigned long request, void *argp)
see ioctl(2) returns success/failure; logs failure at debug level
Definition linux.hpp:216
Expected< struct ::stat > stat(const std::string &pathname)
see stat(2)
Definition linux.hpp:350
Expected execve(const std::string &exec, const std::vector< std::string > &args, const std::vector< std::string > &envp)
args and envp do NOT need to have an extra NULL element; this is added for you. see execve(2) returns...
Definition linux.hpp:111
Expected kill(pid_t pid, int sig)
see kill(2) returns success/failure; logs failure at debug level
Definition linux.hpp:95
Expected< SignalHandlerT > signal(Signal sig, SignalHandlerT handler)
Definition linux.hpp:401
Expected< long > ptrace(int request, pid_t pid=0, AddrT addr=NULL, DataT data=NULL)
see ptrace(2) returns success/failure; logs failure at debug level
Definition linux.hpp:322
Expected close(int fd)
closes a file descriptor. See close(2) returns success/failure; logs failure at debug level
Definition linux.hpp:80
std::string fmt_or_unknown(T &&value, fmt::fstring< T > fmt="{}")
Definition unknown.hpp:11
Definition linux.hpp:140
pid_t pid
Definition linux.hpp:143
@ Child
Definition linux.hpp:141
@ Parent
Definition linux.hpp:141
enum asmgrader::linux::Fork::@0 which
Definition linux.hpp:289
int write_fd
Definition linux.hpp:291
int read_fd
Definition linux.hpp:290