Line data Source code
1 : /*
2 : Copyright 2018-2024, Barcelona Supercomputing Center (BSC), Spain
3 : Copyright 2015-2024, Johannes Gutenberg Universitaet Mainz, Germany
4 :
5 : This software was partially supported by the
6 : EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu).
7 :
8 : This software was partially supported by the
9 : ADA-FS project under the SPPEXA project funded by the DFG.
10 :
11 : This file is part of GekkoFS.
12 :
13 : GekkoFS is free software: you can redistribute it and/or modify
14 : it under the terms of the GNU General Public License as published by
15 : the Free Software Foundation, either version 3 of the License, or
16 : (at your option) any later version.
17 :
18 : GekkoFS is distributed in the hope that it will be useful,
19 : but WITHOUT ANY WARRANTY; without even the implied warranty of
20 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 : GNU General Public License for more details.
22 :
23 : You should have received a copy of the GNU General Public License
24 : along with GekkoFS. If not, see <https://www.gnu.org/licenses/>.
25 :
26 : SPDX-License-Identifier: GPL-3.0-or-later
27 : */
28 :
29 : /* C++ includes */
30 : #include <CLI/CLI.hpp>
31 : #include <nlohmann/json.hpp>
32 : #include <memory>
33 : #include <fmt/format.h>
34 : #include <commands.hpp>
35 : #include <reflection.hpp>
36 : #include <serialize.hpp>
37 : #include <binary_buffer.hpp>
38 :
39 :
40 : /* C includes */
41 : #include <sys/types.h>
42 : #include <sys/stat.h>
43 : #include <fcntl.h>
44 : #include <unistd.h>
45 : #include <sys/xattr.h>
46 :
47 : // Syscall numbers
48 : #include <sys/syscall.h>
49 :
50 : using json = nlohmann::json;
51 :
52 494 : struct syscall_coverage_options {
53 : bool verbose{};
54 : std::string pathname;
55 :
56 :
57 : REFL_DECL_STRUCT(syscall_coverage_options, REFL_DECL_MEMBER(bool, verbose),
58 : REFL_DECL_MEMBER(std::string, pathname));
59 : };
60 :
61 1 : struct syscall_coverage_output {
62 : int retval;
63 : int errnum;
64 : std::string syscall;
65 :
66 : REFL_DECL_STRUCT(syscall_coverage_output, REFL_DECL_MEMBER(int, retval),
67 : REFL_DECL_MEMBER(int, errnum),
68 : REFL_DECL_MEMBER(std::string, syscall));
69 : };
70 :
71 : void
72 1 : to_json(json& record, const syscall_coverage_output& out) {
73 1 : record = serialize(out);
74 1 : }
75 :
76 : void
77 0 : output(const std::string syscall, const int ret,
78 : const syscall_coverage_options& opts) {
79 0 : if(opts.verbose) {
80 0 : fmt::print(
81 : "syscall_coverage[{}](pathname=\"{}\") = {}, errno: {} [{}]\n",
82 0 : syscall, opts.pathname, ret, errno, ::strerror(errno));
83 : }
84 :
85 0 : json out = syscall_coverage_output{ret, errno, syscall};
86 0 : fmt::print("{}\n", out.dump(2));
87 0 : }
88 :
89 : /*
90 : * system calls
91 : * that are being tested.
92 : */
93 : void
94 1 : syscall_coverage_exec(const syscall_coverage_options& opts) {
95 :
96 1 : int fd = ::open(opts.pathname.c_str(), O_RDWR);
97 :
98 1 : if(fd == -1) {
99 0 : output("open", fd, opts);
100 0 : return;
101 : }
102 :
103 : // create external
104 1 : int fdext = ::open("tmpfile", O_RDWR | O_CREAT, 0666);
105 :
106 : // faccessat Internal
107 :
108 1 : auto rv = ::faccessat(AT_FDCWD, opts.pathname.c_str(), F_OK, 0);
109 :
110 1 : if(rv < 0) {
111 0 : output("faccessat", rv, opts);
112 0 : return;
113 : }
114 :
115 : // faccessat External
116 :
117 1 : rv = ::faccessat(AT_FDCWD, "/tmp", F_OK, 0);
118 :
119 1 : if(rv < 0) {
120 0 : output("faccessat, external", rv, opts);
121 0 : return;
122 : }
123 :
124 : // lstat
125 1 : struct stat st;
126 1 : rv = ::lstat(opts.pathname.c_str(), &st);
127 1 : if(rv < 0) {
128 0 : output("lstat", rv, opts);
129 0 : return;
130 : }
131 :
132 1 : rv = ::lstat("tmpfile", &st);
133 1 : if(rv < 0) {
134 0 : output("lstat", rv, opts);
135 0 : return;
136 : }
137 :
138 : // pwrite external
139 1 : rv = ::pwrite(fdext, "test", 4, 0);
140 1 : if(rv < 0) {
141 0 : output("pwrite", rv, opts);
142 0 : return;
143 : }
144 :
145 : // pread external
146 1 : char bufext[4];
147 1 : rv = ::pread(fdext, bufext, 4, 0);
148 1 : if(rv < 0) {
149 0 : output("pread", rv, opts);
150 0 : return;
151 : }
152 :
153 : // lseek external
154 1 : rv = ::lseek(fdext, 0, SEEK_SET);
155 1 : if(rv < 0) {
156 0 : output("lseek", rv, opts);
157 0 : return;
158 : }
159 :
160 : // ftruncate external
161 1 : rv = ::ftruncate(fdext, 0);
162 1 : if(rv < 0) {
163 0 : output("ftruncate", rv, opts);
164 0 : return;
165 : }
166 :
167 : // truncate exterlan
168 1 : rv = ::truncate("tmpfile", 0);
169 1 : if(rv < 0) {
170 0 : output("truncate", rv, opts);
171 0 : return;
172 : }
173 :
174 :
175 : // dup external
176 1 : int fdext2 = ::dup(fdext);
177 1 : if(fdext2 < 0) {
178 0 : output("dup", fdext2, opts);
179 0 : return;
180 : }
181 :
182 : // dup2 external
183 1 : int ffdext3 = 0;
184 1 : rv = ::dup2(fdext, ffdext3);
185 1 : if(rv < 0) {
186 0 : output("dup2", rv, opts);
187 0 : return;
188 : }
189 :
190 :
191 : // fchmod internal
192 1 : rv = ::fchmod(fd, 0777);
193 1 : if(errno != ENOTSUP) {
194 0 : output("fchmod", rv, opts);
195 0 : return;
196 : }
197 :
198 : // fchmod external
199 1 : rv = ::fchmod(fdext, 0777);
200 1 : if(rv < 0) {
201 0 : output("fchmod", rv, opts);
202 0 : return;
203 : }
204 :
205 : // fchmodat internal
206 1 : rv = ::fchmodat(AT_FDCWD, opts.pathname.c_str(), 0777, 0);
207 1 : if(errno != ENOTSUP) {
208 0 : output("fchmodat", rv, opts);
209 0 : return;
210 : }
211 : // fchmodat external
212 1 : rv = ::fchmodat(AT_FDCWD, "tmpfile", 0777, 0);
213 1 : if(rv < 0) {
214 0 : output("fchmodat, external", rv, opts);
215 0 : return;
216 : }
217 :
218 : // dup3 internal
219 1 : rv = ::dup3(fd, 0, 0);
220 1 : if(errno != ENOTSUP) {
221 0 : output("dup3", rv, opts);
222 0 : return;
223 : }
224 :
225 : // dup3 external
226 1 : int ffdext4 = 0;
227 1 : rv = ::dup3(fdext, ffdext4, 0);
228 1 : if(rv < 0) {
229 0 : output("dup3", rv, opts);
230 0 : return;
231 : }
232 :
233 :
234 : // fcntl
235 1 : rv = ::fcntl(fd, F_GETFD);
236 1 : if(rv < 0) {
237 0 : output("fcntl, F_GETFD", rv, opts);
238 0 : return;
239 : }
240 :
241 1 : rv = ::fcntl(fd, F_GETFL);
242 1 : if(rv < 0 || rv != O_RDWR) {
243 0 : output("fcntl, F_GETFL", rv, opts);
244 0 : return;
245 : }
246 :
247 :
248 1 : rv = ::fcntl(fd, F_SETFD, 0);
249 1 : if(rv < 0) {
250 0 : output("fcntl, F_SETFD", rv, opts);
251 0 : return;
252 : }
253 :
254 1 : rv = ::fcntl(fd, F_SETFL, 0);
255 1 : if(errno != ENOTSUP) {
256 0 : output("fcntl, F_SETFL", rv, opts);
257 0 : return;
258 : }
259 :
260 1 : rv = ::fcntl(fd, F_DUPFD, 0);
261 1 : if(rv < 0) {
262 0 : output("fcntl, F_DUPFD", rv, opts);
263 0 : return;
264 : }
265 :
266 1 : rv = ::fcntl(fd, F_DUPFD_CLOEXEC, 0);
267 1 : if(rv < 0) {
268 0 : output("fcntl, F_DUPFD_CLOEXEC", rv, opts);
269 0 : return;
270 : }
271 :
272 : // Fstatfs internal
273 :
274 1 : struct statfs stfs;
275 1 : rv = ::fstatfs(fd, &stfs);
276 1 : if(rv < 0) {
277 0 : output("fstatfs", rv, opts);
278 0 : return;
279 : }
280 :
281 : // Fstatfs external
282 1 : rv = ::fstatfs(fdext, &stfs);
283 1 : if(rv < 0) {
284 0 : output("fstatfs", rv, opts);
285 0 : return;
286 : }
287 :
288 : // fsync
289 :
290 1 : rv = ::fsync(fd);
291 1 : if(rv < 0) {
292 0 : output("fsync", rv, opts);
293 0 : return;
294 : }
295 :
296 : // getxattr
297 :
298 1 : char buf[1024];
299 1 : rv = ::getxattr(opts.pathname.c_str(), "user.test", buf, sizeof(buf));
300 1 : if(errno != ENOTSUP) {
301 0 : output("getxattr", rv, opts);
302 0 : return;
303 : }
304 :
305 : // readlinkat
306 1 : rv = ::readlinkat(AT_FDCWD, opts.pathname.c_str(), buf, sizeof(buf));
307 1 : if(errno != ENOTSUP) {
308 0 : output("readlinkat", rv, opts);
309 0 : return;
310 : }
311 :
312 : // chdir internal error
313 1 : rv = ::chdir(opts.pathname.c_str());
314 1 : if(errno != ENOTDIR) {
315 0 : output("chdir", rv, opts);
316 0 : return;
317 : }
318 :
319 : // chdir internal error
320 2 : std::string nonexist = opts.pathname+"x2";
321 1 : rv = ::chdir(nonexist.c_str());
322 1 : if(rv >= 0) {
323 0 : output("chdir", rv, opts);
324 0 : return;
325 : }
326 :
327 : // fchdir
328 1 : auto fddir = ::open(".", O_RDONLY);
329 1 : if(fddir < 0) {
330 0 : output("fchdir", fddir, opts);
331 0 : return;
332 : }
333 :
334 1 : rv = ::fchdir(fddir);
335 1 : if(rv < 0) {
336 0 : output("fchdir", rv, opts);
337 0 : return;
338 : }
339 :
340 : // ftruncate
341 1 : rv = ::ftruncate(fd, 0);
342 1 : if(rv < 0) {
343 0 : output("ftruncate", rv, opts);
344 0 : return;
345 : }
346 :
347 : // fchdir internal file
348 1 : rv = ::fchdir(fd);
349 1 : if(errno != EBADF) {
350 0 : output("fchdir", rv, opts);
351 0 : return;
352 : }
353 :
354 :
355 : // fchdir directory from opts.pathname
356 1 : auto fd2 = ::open(
357 1 : opts.pathname.substr(0, opts.pathname.find_last_of("/")).c_str(),
358 : O_RDONLY);
359 1 : if(fd2 < 0) {
360 0 : output("fchdir", fd2, opts);
361 0 : return;
362 : }
363 1 : rv = ::fchdir(fd2);
364 1 : if(rv < 0) {
365 0 : output("fchdir", rv, opts);
366 0 : return;
367 : }
368 :
369 2 : std::string pid = std::to_string(getpid());
370 2 : std::string path1 = "/tmp/"+pid+"test_rename";
371 2 : std::string path2 = "/tmp/"+pid+"test_rename2";
372 :
373 : // renameat external
374 1 : auto fdtmp = ::open(path1.c_str(), O_CREAT | O_WRONLY, 0644);
375 1 : ::close(fdtmp);
376 :
377 1 : rv = ::renameat(AT_FDCWD, path1.c_str(), AT_FDCWD,
378 : opts.pathname.c_str());
379 1 : if(errno != ENOTSUP) {
380 0 : output("renameat_ext_to_int", rv, opts);
381 0 : return;
382 : }
383 :
384 1 : rv = ::renameat(AT_FDCWD, path1.c_str(), AT_FDCWD, path2.c_str());
385 1 : if(rv < 0) {
386 0 : output("renameat_ext_to_ext", rv, opts);
387 0 : return;
388 : }
389 :
390 : // open with O_APPEND
391 2 : std::string path_append = "/tmp/" + pid + "test_append";
392 1 : auto fd_append = ::open(path1.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0644);
393 1 : if(fd_append < 0) {
394 0 : output("open with O_APPEND", fd_append, opts);
395 0 : return;
396 : }
397 1 : rv = ::write(fd_append, "testappend", 10);
398 1 : if(rv < 0) {
399 0 : output("open with O_APPEND", rv, opts);
400 0 : return;
401 : }
402 1 : ::close(fd_append);
403 :
404 : // sys_open
405 1 : rv = ::syscall(SYS_open, opts.pathname.c_str(), O_RDONLY, 0);
406 1 : if(rv < 0) {
407 0 : output("sys_open", rv, opts);
408 0 : return;
409 : }
410 :
411 : // sys_creat
412 1 : rv = ::syscall(SYS_creat, opts.pathname.c_str(), 0777);
413 1 : if(rv < 0) {
414 0 : output("sys_creat", rv, opts);
415 0 : return;
416 : }
417 :
418 : // sys_unlinkat
419 1 : rv = ::syscall(SYS_unlinkat, AT_FDCWD, opts.pathname.c_str(), 0);
420 1 : if(rv < 0) {
421 0 : output("sys_unlinkat", rv, opts);
422 0 : return;
423 : }
424 :
425 : // sys_mkdirat
426 2 : std::string path = opts.pathname + "path";
427 1 : rv = ::syscall(SYS_mkdirat, AT_FDCWD, opts.pathname.c_str(), 0777);
428 1 : if(rv < 0) {
429 0 : output("sys_mkdirat", rv, opts);
430 0 : return;
431 : }
432 :
433 : // SYS_chmod
434 1 : rv = ::syscall(SYS_chmod, opts.pathname.c_str(), 0777);
435 1 : if(errno != ENOTSUP) {
436 0 : output("sys_chmod", rv, opts);
437 0 : return;
438 : }
439 :
440 : // hook_faccessat coverage
441 1 : rv = ::syscall(SYS_faccessat, AT_FDCWD, opts.pathname.c_str(), F_OK, 0);
442 1 : if(rv < 0) {
443 0 : output("sys_faccessat", rv, opts);
444 0 : return;
445 : }
446 :
447 1 : rv = ::syscall(SYS_faccessat, AT_FDCWD, "/tmp", F_OK, 0);
448 1 : if(rv < 0) {
449 0 : output("sys_faccessat", rv, opts);
450 0 : return;
451 : }
452 :
453 :
454 1 : rv = 0;
455 1 : errno = 0;
456 1 : auto syscall = "ALLOK";
457 2 : json out = syscall_coverage_output{(int) rv, errno, syscall};
458 1 : fmt::print("{}\n", out.dump(2));
459 1 : return;
460 : }
461 :
462 : void
463 247 : syscall_coverage_init(CLI::App& app) {
464 :
465 : // Create the option and subcommand objects
466 247 : auto opts = std::make_shared<syscall_coverage_options>();
467 247 : auto* cmd =
468 247 : app.add_subcommand("syscall_coverage", "Execute severals syscalls");
469 :
470 : // Add options to cmd, binding them to opts
471 247 : cmd->add_flag("-v,--verbose", opts->verbose,
472 494 : "Produce human writeable output");
473 :
474 494 : cmd->add_option("pathname", opts->pathname, "File name")
475 : ->required()
476 494 : ->type_name("");
477 :
478 989 : cmd->callback([opts]() { syscall_coverage_exec(*opts); });
479 247 : }
|