libclues
Linux C++ Tracing Library
Loading...
Searching...
No Matches
fs.cxx
1// C++
2#include <sstream>
3
4// cosmos
5#include <cosmos/compiler.hxx>
6#include <cosmos/error/RuntimeError.hxx>
7#include <cosmos/fs/filesystem.hxx>
8#include <cosmos/fs/types.hxx>
9#include <cosmos/string.hxx>
10
11// clues
12#include <clues/Engine.hxx>
13#include <clues/format.hxx>
14#include <clues/items/fs.hxx>
15#include <clues/macros.h>
16#include <clues/syscalls/fs.hxx>
17#include <clues/sysnrs/generic.hxx>
18#include <clues/Tracee.hxx>
19// private
20#include <clues/private/kernel/stat.hxx>
21#include <clues/private/kernel/dirent.hxx>
22#include <clues/private/utils.hxx>
23
24namespace clues::item {
25
26std::string FileDescriptor::str() const {
27 if (m_at_semantics && m_fd == cosmos::FileNum::AT_CWD)
28 return "AT_FDCWD";
29
30 auto ret = std::to_string(cosmos::to_integral(m_fd));
31
32 if (m_info) {
33 ret += format::fd_info(*m_info);
34 }
35
36 return ret;
37}
38
41
42 if (proc.engine().formatFlags()[Engine::FormatFlag::FD_INFO]) {
43 m_info.reset();
44
45 const auto &map = proc.fdInfoMap();
46
47 if (auto it = map.find(m_fd); it != map.end()) {
48 m_info = it->second;
49 }
50 }
51}
52
53std::string OpenFlagsValue::str() const {
54 /*
55 * When compling for x86_64 then O_LARGEFILE is defined to 0, which won't work
56 * when tracing 32-bit emulation binaries. The purpose of O_LARGEFILE being
57 * defined to 0 on x86_64 is to avoid passing the bit to `open()`
58 * unnecessarily.
59 * For tracing, in the context of this compilation unit, it should be okay to
60 * redefine the value to the actual O_LARGEFILE bit to make it visible when
61 * tracing 32-bit emulation binaries.
62 * Hopefully the value is the same on other architectures as well.
63 */
64#if O_LARGEFILE == 0
65# define RESTORE_LARGEFILE
66# pragma push_macro("O_LARGEFILE")
67# undef O_LARGEFILE
68# define O_LARGEFILE 0100000
69#endif
70
71 BITFLAGS_FORMAT_START_COMBINED(m_flags, valueAs<int>());
72
73 switch (m_mode) {
74 default: BITFLAGS_STREAM() << "O_???"; break;
75 case cosmos::OpenMode::READ_ONLY: BITFLAGS_STREAM() << "O_RDONLY"; break;
76 case cosmos::OpenMode::WRITE_ONLY: BITFLAGS_STREAM() << "O_WRONLY"; break;
77 case cosmos::OpenMode::READ_WRITE: BITFLAGS_STREAM() << "O_RDWR"; break;
78 }
79
80 BITFLAGS_STREAM() << '|';
81
82 BITFLAGS_ADD(O_APPEND);
83 BITFLAGS_ADD(O_ASYNC);
84 BITFLAGS_ADD(O_CLOEXEC);
85 BITFLAGS_ADD(O_CREAT);
86 BITFLAGS_ADD(O_DIRECT);
87 BITFLAGS_ADD(O_DIRECTORY);
88 BITFLAGS_ADD(O_DSYNC);
89 BITFLAGS_ADD(O_EXCL);
90 BITFLAGS_ADD(O_LARGEFILE);
91 BITFLAGS_ADD(O_NOATIME);
92 BITFLAGS_ADD(O_NOCTTY);
93 BITFLAGS_ADD(O_NOFOLLOW);
94 BITFLAGS_ADD(O_NONBLOCK);
95 BITFLAGS_ADD(O_PATH);
96 BITFLAGS_ADD(O_SYNC);
97 BITFLAGS_ADD(O_TMPFILE);
98 BITFLAGS_ADD(O_TRUNC);
99
100 return BITFLAGS_STR();
101#ifdef RESTORE_LARGEFILE
102# pragma pop_macro("O_LARGEFILE")
103#endif
104}
105
107 const auto raw = valueAs<int>();
108 // the access mode consists of the lower two bits
109 m_mode = cosmos::OpenMode{raw & 0x3};
110 m_flags = cosmos::OpenFlags{raw & ~0x3};
111}
112
113std::string AtFlagsValue::str() const {
114 BITFLAGS_FORMAT_START(m_flags);
115
116 BITFLAGS_ADD(AT_EMPTY_PATH);
117 BITFLAGS_ADD(AT_SYMLINK_NOFOLLOW);
118 BITFLAGS_ADD(AT_SYMLINK_FOLLOW);
119 /*
120 * some AT_ constants share the same values and are system call
121 * dependent, thus we need to check the context here.
122 */
123 if (m_call->callNr() == SystemCallNr::UNLINKAT)
124 BITFLAGS_ADD(AT_REMOVEDIR);
125 if (m_call->callNr() == SystemCallNr::FACCESSAT2)
126 BITFLAGS_ADD(AT_EACCESS);
127 if (m_call->callNr() == SystemCallNr::FSTATAT64 || m_call->callNr() == SystemCallNr::NEWFSTATAT)
128 BITFLAGS_ADD(AT_NO_AUTOMOUNT);
129
130 return BITFLAGS_STR();
131}
132
134 m_flags = AtFlags{valueAs<int>()};
135}
136
138 m_checks = cosmos::fs::AccessChecks(valueAs<int>());
139}
140
141std::string AccessModeParameter::str() const {
142 using cosmos::fs::AccessCheck;
143
144 std::stringstream ss;
145
146 if (m_checks.none()) {
147 ss << "F_OK";
148 } else {
149 if (m_checks[AccessCheck::READ_OK]) {
150 ss << "R_OK|";
151 }
152 if (m_checks[AccessCheck::WRITE_OK]) {
153 ss << "W_OK|";
154 }
155 if (m_checks[AccessCheck::EXEC_OK]) {
156 ss << "X_OK";
157 }
158 }
159
160 auto ret = ss.str();
161
162 if (ret.empty()) {
163 return "???";
164 }
165
166 return strip_back(std::move(ret));
167}
168
169std::string FileModeParameter::str() const {
170 return format::file_mode_numeric(m_mode.mask()) +
171 " (" + m_mode.symbolic() + ")";
172}
173
174std::string StatParameter::str() const {
175 if (!m_stat) {
176 if (isZero()) {
177 return "NULL";
178 } else {
179 return "<invalid>";
180 }
181 }
182
183 std::stringstream ss;
184
185 const auto st = *m_stat->raw();
186 using namespace std::string_literals;
187
188 const auto is_old_stat = isOldStat();
189
190 ss
191 << "{"
192 << "ino=" << st.st_ino << ", "
193 << "dev=" << format::device_id(m_stat->device()) << ", "
194 << "mode=" << format::file_type(m_stat->type()) << "|" << format::file_mode_numeric(m_stat->mode().mask()) << ", "
195 << "nlink=" << m_stat->numLinks() << ", "
196 << "uid=" << st.st_uid << ", "
197 << "gid=" << st.st_gid << ", "
198 << (m_stat->type().isCharDev() || m_stat->type().isBlockDev() ? "rdev="s + format::device_id(m_stat->representedDevice()) + ", " : "")
199 << "size=" << st.st_size << ", ";
200
201 if (!is_old_stat) {
202 /*
203 * these fields do not exist in oldstat syscalls
204 */
205 ss << "blksize=" << st.st_blksize << ", " << "blocks=" << st.st_blocks << ", ";
206 }
207
208 ss
209 << "atim=" << format::timespec(m_stat->accessTime(), is_old_stat) << ", "
210 << "mtim=" << format::timespec(m_stat->modTime(), is_old_stat) << ", "
211 << "ctim=" << format::timespec(m_stat->statusTime(), is_old_stat)
212 << "}";
213
214 return ss.str();
215}
216
218 using enum SystemCallNr;
219
220 switch (m_call->callNr()) {
221 case OLDSTAT: [[fallthrough]];
222 case OLDLSTAT: [[fallthrough]];
223 case OLDFSTAT: return true;
224 default: return false;
225 }
226}
227
229 using enum SystemCallNr;
230
231 switch (m_call->callNr()) {
232 case STAT64: [[fallthrough]];
233 case LSTAT64: [[fallthrough]];
234 case FSTAT64: [[fallthrough]];
235 case FSTATAT64: return true;
236 default: return false;
237 }
238}
239
241 using enum SystemCallNr;
242
243 switch (m_call->callNr()) {
244 case SystemCallNr::STAT: [[fallthrough]];
245 case SystemCallNr::LSTAT: [[fallthrough]];
246 case SystemCallNr::FSTAT: return true;
247 default: return false;
248 }
249}
250
252 if (!m_stat) {
253 m_stat = std::make_optional<cosmos::FileStatus>();
254 }
255
256#if defined(COSMOS_X86_64) || defined(COSMOS_AARCH64)
257 /*
258 * on 64-bit platforms life is simple, we directly copy the data into
259 * a cosmos::FileStatus struct.
260 * make sure the raw kernel data structure `stat64` matches `struct
261 * stat` and then copy everything into it.
262 */
263 static_assert(sizeof(*m_stat->raw()) == sizeof(stat64));
264#else
265 /*
266 * on modern 32-bit we can still do the same for the stat64 family of
267 * system calls, but only if the tracer is 32-bit as well...
268 */
269 static_assert(sizeof(*m_stat->raw()) == sizeof(stat32_64));
270#endif
271
272 auto fetch_and_copy = [this, &proc]<typename STAT>() {
273 STAT st;
274 if (!proc.readStruct(asPtr(), st)) {
275 m_stat.reset();
276 return;
277 }
278
279 auto &raw = *m_stat->raw();
280 /*
281 * to be layout agnostic simply copy over field-by-field
282 */
283 raw.st_dev = st.dev;
284 raw.st_ino = st.ino;
285 raw.st_mode = st.mode;
286 raw.st_nlink = st.nlink;
287 raw.st_uid = st.uid;
288 raw.st_gid = st.gid;
289 raw.st_rdev = st.rdev;
290 raw.st_size = st.size;
291
292 if constexpr (std::is_same_v<STAT, old_kernel_stat>) {
293 /* the old kernel stat is missing a lot of stuff */
294 raw.st_atim.tv_sec = st.atime;
295 raw.st_atim.tv_nsec = 0;
296 raw.st_mtim.tv_sec = st.mtime;
297 raw.st_mtim.tv_nsec = 0;
298 raw.st_ctim.tv_sec = st.ctime;
299 raw.st_ctim.tv_nsec = 0;
300 raw.st_blksize = 0;
301 raw.st_blocks = 0;
302 }
303
304 if constexpr (!std::is_same_v<STAT, old_kernel_stat>) {
305 raw.st_blksize = st.blksize;
306 raw.st_blocks = st.blocks;
307 raw.st_atim.tv_sec = st.atime;
308 raw.st_atim.tv_nsec = st.atime_nsec;
309 raw.st_mtim.tv_sec = st.mtime;
310 raw.st_mtim.tv_nsec = st.mtime_nsec;
311 raw.st_ctim.tv_sec = st.ctime;
312 raw.st_ctim.tv_nsec = st.ctime_nsec;
313 }
314 };
315
316 switch (m_call->abi()) {
317 case ABI::X86_64: [[fallthrough]];
318 case ABI::X32: [[fallthrough]];
319 case ABI::AARCH64: {
320 /*
321 * there's only one type of stat
322 * directly read into libcosmos's FileStatus
323 */
324 if (!proc.readStruct(asPtr(), *m_stat->raw())) {
325 m_stat.reset();
326 }
327 break;
328 } case ABI::I386: {
329 /*
330 * on 32-bit platforms things get messy, we have three
331 * different possibilities using three different data
332 * structures.
333 */
334 if (isOldStat()) {
335 fetch_and_copy.operator()<old_kernel_stat>();
336 } else if (isStat64()) {
337 fetch_and_copy.operator()<stat32_64>();
338 } else if (isRegularStat()) {
339 // regular 32-bit stat
340 fetch_and_copy.operator()<stat32>();
341 } else {
342 throw cosmos::RuntimeError{"unexpected syscall nr for struct stat"};
343 }
344 break;
345 } default: throw cosmos::RuntimeError{"unexpected ABI for struct stat"};
346 }
347}
348
349static std::string_view entry_type_label(const cosmos::DirEntry::Type type) {
350 switch (cosmos::to_integral(type)) {
351 CASE_ENUM_TO_STR(DT_BLK);
352 CASE_ENUM_TO_STR(DT_CHR);
353 CASE_ENUM_TO_STR(DT_DIR);
354 CASE_ENUM_TO_STR(DT_FIFO);
355 CASE_ENUM_TO_STR(DT_LNK);
356 CASE_ENUM_TO_STR(DT_REG);
357 CASE_ENUM_TO_STR(DT_SOCK);
358 CASE_ENUM_TO_STR(DT_UNKNOWN);
359 default: return "???";
360 }
361}
362
363std::string DirEntries::str() const {
364 std::stringstream ss;
365 auto result = m_call->result();
366
367 if (!result) {
368 ss << "undefined";
369 } else if (m_entries.empty()) {
370 ss << "empty";
371 } else {
372 ss << m_entries.size() << " entries: ";
373
374 std::string comma;
375
376 for (const auto &entry: m_entries) {
377 ss << comma << "{d_ino=" << entry.inode
378 << ", d_off=" << entry.offset
379 << ", d_reclen=" << offsetof(struct linux_dirent, d_name) + entry.name.length() + 2
380 << ", d_name=\"" << entry.name
381 << "\", d_type=" << entry_type_label(entry.type)
382 << "}";
383
384 if (comma.empty()) {
385 comma = ", ";
386 }
387 }
388 }
389
390 return ss.str();
391}
392
394 // the amount of data stored at the DirEntries location depends on the
395 // system call result value.
396 auto result = m_call->result();
397 if (!result)
398 // error occurred
399 return;
400
401 const auto bytes = result->valueAs<size_t>();
402 if (bytes == 0)
403 // empty
404 return;
405
406 /*
407 * first copy over all the necessary data from the tracee
408 */
409 m_buffer = std::unique_ptr<char[]>(new char[bytes]);
410 proc.readBlob(asPtr(), m_buffer.get(), bytes);
411
412 if (m_call->callNr() == SystemCallNr::GETDENTS64) {
413 parseEntries64(bytes);
414 } else {
415 if (m_call->is32BitEmulationABI()) {
417 } else {
419 }
420 }
421}
422
423template <typename DIRENT>
424void DirEntries::parseEntries32(const size_t bytes) {
425
426 DIRENT *cur = nullptr;
427 size_t pos = 0;
428 constexpr auto NAME_OFFSET = offsetof(DIRENT, d_name);
429
430 while (pos < bytes) {
431 cur = (DIRENT*)(m_buffer.get() + pos);
432 auto namelen = cur->d_reclen - NAME_OFFSET - 2;
433 /*
434 * the length can still include padding, thus look from the
435 * back for the first non-zero byte at the end to correct the
436 * length.
437 */
438 for (char *last = cur->d_name + namelen - 1; last > cur->d_name && *last == '\0'; last--, namelen--);
439
440 const auto type = *(m_buffer.get() + pos + cur->d_reclen - 1);
441
442 m_entries.emplace_back(Entry{
443 cur->d_ino,
444 static_cast<int64_t>(cur->d_off),
445 // to avoid copying we only keep a string_view in
446 // Entry pointing to the raw buffer data.
447 std::string_view{cur->d_name, namelen},
448 cosmos::DirEntry::Type{static_cast<unsigned char>(type)}
449 });
450 pos += cur->d_reclen;
451 }
452}
453
454void DirEntries::parseEntries64(const size_t bytes) {
455 struct linux_dirent64 *cur = nullptr;
456 size_t pos = 0;
457 constexpr auto NAME_OFFSET = offsetof(linux_dirent64, d_name);
458
459 while (pos < bytes) {
460 cur = (linux_dirent64*)(m_buffer.get() + pos);
461 auto namelen = cur->d_reclen - NAME_OFFSET;
462 /*
463 * remove padding from length
464 */
465 for (char *last = cur->d_name + namelen - 1; last > cur->d_name && *last == '\0'; last--, namelen--);
466
467 m_entries.emplace_back(Entry{
468 cur->d_ino,
469 cur->d_off,
470 std::string_view{cur->d_name, namelen},
471 cosmos::DirEntry::Type{static_cast<unsigned char>(cur->d_type)}
472 });
473
474 pos += cur->d_reclen;
475 }
476}
477
478} // end ns
@ FD_INFO
print detailed file descriptor information
Definition Engine.hxx:50
const SystemCall * m_call
The system call context this item part of.
bool isZero() const
Returns whether the parameter is set to 0 / NULL.
OTHER valueAs() const
Helper to cast the strongly typed Word m_val to other strong enum types.
Base class for traced processes.
Definition Tracee.hxx:39
bool readStruct(const ForeignPtr addr, T &out) const
Reads a system call struct from the tracee's address space into out.
Definition Tracee.hxx:221
const FDInfoMap & fdInfoMap() const
Provides access to the current knowledge about file descriptors in the tracee.
Definition Tracee.hxx:87
void readBlob(const ForeignPtr addr, char *buffer, const size_t bytes) const
Reads an arbitrary binary blob of fixed length from the tracee.
Definition Tracee.cxx:981
std::string str() const override
Returns a human readable string representation of the item.
Definition fs.cxx:141
void processValue(const Tracee &) override
Processes the value stored in m_val acc. to the actual item type.
Definition fs.cxx:137
void processValue(const Tracee &) override
Processes the value stored in m_val acc. to the actual item type.
Definition fs.cxx:133
std::string str() const override
Returns a human readable string representation of the item.
Definition fs.cxx:113
void parseEntries32(const size_t bytes)
Parses the dirent structures from m_buffer.
Definition fs.cxx:424
std::string str() const override
Returns a human readable string representation of the item.
Definition fs.cxx:363
void updateData(const Tracee &proc) override
Called upon exit of the system call to update possible out parameters.
Definition fs.cxx:393
std::unique_ptr< char[]> m_buffer
the raw buffer backing m_entries
Definition fs.hxx:280
void parseEntries64(const size_t bytes)
Parses the dirent structures for getdents64().
Definition fs.cxx:454
std::optional< FDInfo > m_info
filled if FormatFlag::FD_INFO is set.
Definition fs.hxx:69
std::string str() const override
Returns a human readable string representation of the item.
Definition fs.cxx:26
void processValue(const Tracee &) override
Processes the value stored in m_val acc. to the actual item type.
Definition fs.cxx:39
std::string str() const override
Returns a human readable string representation of the item.
Definition fs.cxx:169
std::string str() const override
Returns a human readable string representation of the item.
Definition fs.cxx:53
void processValue(const Tracee &) override
Processes the value stored in m_val acc. to the actual item type.
Definition fs.cxx:106
std::string str() const override
Returns a human readable string representation of the item.
Definition fs.cxx:174
bool isOldStat() const
Whether this is related to one of the OLDSTAT family of system calls.
Definition fs.cxx:217
bool isRegularStat() const
Whether this is related to one of the STAT family of system calls.
Definition fs.cxx:240
void updateData(const Tracee &proc) override
Called upon exit of the system call to update possible out parameters.
Definition fs.cxx:251
bool isStat64() const
Whether this is related to one of the STAT64 family of system calls.
Definition fs.cxx:228
@ X32
X86_64 with 32-bit pointers.
Definition types.hxx:66
SystemCallNr
Abstract system call number usable across architectures and ABIs.
Definition generic.hxx:29
64-bit entries used with getdents64() on both 32-bit and 64-bit ABIs
Definition dirent.hxx:36
32-bit getdents() directory entries.used on native 32-bit/64-bit ABIs.
Definition dirent.hxx:14
Original Linux stat data structure for SystemCallNr::OLDSTAT, OLDLSTAT and OLDFSTAT.
Definition stat.hxx:14
64-bit sized stat data structure used with the stat64 family of system calls on 32-bit ABIs like i386...
Definition stat.hxx:57
32-bit sized stat data structure with some padding for SystemCallNR::STAT, LSTAT and FSTAT on 32-bit ...
Definition stat.hxx:29
The single stat structure from asm-generic used e.g. on AARCH64.
Definition stat.hxx:106