libclues
Linux C++ Tracing Library
Loading...
Searching...
No Matches
fcntl.cxx
1// C++
2#include <sstream>
3
4// cosmos
5#include <cosmos/error/RuntimeError.hxx>
6#include <cosmos/formatting.hxx>
7
8// clues
9#include <clues/Tracee.hxx>
10#include <clues/items/fcntl.hxx>
11#include <clues/macros.h>
12#include <clues/private/utils.hxx>
13#include <clues/syscalls/fs.hxx>
14
15namespace clues::item {
16
17std::string FileDescFlagsValue::str() const {
18 BITFLAGS_FORMAT_START(m_flags);
19
20 BITFLAGS_ADD(FD_CLOEXEC);
21
22 return BITFLAGS_STR();
23}
24
26 m_flags = cosmos::FileDescriptor::DescFlags{valueAs<int>()};
27}
28
30 m_op = valueAs<Oper>();
31}
32
33std::string FcntlOperation::str() const {
34 /*
35 * the F_*LK definitions exported by userspace headers are not helpful
36 * for our tracer context at all. They have different values on 64-bit
37 * targets than on 32-bit targets without _FILE_OFFSET_BITS=64 than on
38 * 32-bit targets with the latter define. They don't relate to what
39 * happens on system call level at all.
40 *
41 * Thus get rid of the macro definitions and use our own literal
42 * constants.
43 */
44#pragma push_macro("F_GETLK")
45#pragma push_macro("F_GETLK64")
46#pragma push_macro("F_SETLK")
47#pragma push_macro("F_SETLK64")
48#pragma push_macro("F_SETLKW")
49#pragma push_macro("F_SETLKW64")
50#undef F_GETLK
51#undef F_SETLK
52#undef F_SETLKW
53#undef F_GETLK64
54#undef F_SETLK64
55#undef F_SETLKW64
56
57 constexpr int F_GETLK = cosmos::to_integral(Oper::GETLK);
58 constexpr int F_GETLK64 = cosmos::to_integral(Oper::GETLK64);
59 constexpr int F_SETLK = cosmos::to_integral(Oper::SETLK);
60 constexpr int F_SETLK64 = cosmos::to_integral(Oper::SETLK64);
61 constexpr int F_SETLKW = cosmos::to_integral(Oper::SETLKW);
62 constexpr int F_SETLKW64 = cosmos::to_integral(Oper::SETLKW64);
63
64 switch (cosmos::to_integral(m_op)) {
65 CASE_ENUM_TO_STR(F_DUPFD);
66 CASE_ENUM_TO_STR(F_DUPFD_CLOEXEC);
67 CASE_ENUM_TO_STR(F_GETFD);
68 CASE_ENUM_TO_STR(F_SETFD);
69 CASE_ENUM_TO_STR(F_GETFL);
70 CASE_ENUM_TO_STR(F_SETFL);
71 CASE_ENUM_TO_STR(F_GETLK);
72 CASE_ENUM_TO_STR(F_SETLK);
73 CASE_ENUM_TO_STR(F_SETLKW);
74 CASE_ENUM_TO_STR(F_GETLK64);
75 CASE_ENUM_TO_STR(F_SETLK64);
76 CASE_ENUM_TO_STR(F_SETLKW64);
77 CASE_ENUM_TO_STR(F_OFD_SETLK);
78 CASE_ENUM_TO_STR(F_OFD_SETLKW);
79 CASE_ENUM_TO_STR(F_OFD_GETLK);
80 CASE_ENUM_TO_STR(F_GETOWN);
81 CASE_ENUM_TO_STR(F_SETOWN);
82 CASE_ENUM_TO_STR(F_GETOWN_EX);
83 CASE_ENUM_TO_STR(F_SETOWN_EX);
84 CASE_ENUM_TO_STR(F_GETSIG);
85 CASE_ENUM_TO_STR(F_SETSIG);
86 CASE_ENUM_TO_STR(F_SETLEASE);
87 CASE_ENUM_TO_STR(F_GETLEASE);
88 CASE_ENUM_TO_STR(F_NOTIFY);
89 CASE_ENUM_TO_STR(F_SETPIPE_SZ);
90 CASE_ENUM_TO_STR(F_GETPIPE_SZ);
91 CASE_ENUM_TO_STR(F_ADD_SEALS);
92 CASE_ENUM_TO_STR(F_GET_SEALS);
93 CASE_ENUM_TO_STR(F_GET_RW_HINT);
94 CASE_ENUM_TO_STR(F_SET_RW_HINT);
95 CASE_ENUM_TO_STR(F_GET_FILE_RW_HINT);
96 CASE_ENUM_TO_STR(F_SET_FILE_RW_HINT);
97 default: return "F_???";
98 }
99#pragma pop_macro("F_GETLK")
100#pragma pop_macro("F_GETLK64")
101#pragma pop_macro("F_SETLK")
102#pragma pop_macro("F_SETLK64")
103#pragma pop_macro("F_SETLKW")
104#pragma pop_macro("F_SETLKW64")
105}
106
107namespace kernel {
108
109/*
110 * to make things around `struct flock` still more confusing, there are
111 * different alignments of the structure between i386 and x86_64 regarding
112 * `struct flock64`. When compiled in 64-bit context it has 32 bytes
113 * size, when compiled in 32-bit context it has 24 bytes size. For this reason
114 * we need a dedicated compat flock64 when tracing 32-bit emulation binaries.
115 *
116 * see also `compat_flock64` in include/linux/compat.h of the kernel sources.
117 */
118template <typename OFF_T>
119struct flock_t {
120 short l_type;
121 short l_whence;
122 OFF_T l_start;
123 OFF_T l_len;
124 pid_t l_pid;
125} __attribute__((packed));
126
127// 32-bit `off_t` structure which is only used on I386 with fcntl() or fcntl64() when passing GETLK/SETLK/SETLKW
128using flock32 = flock_t<int32_t>;
129// 64-bit `off_t` structure which is only used on I386 with fcntl64() when passing GETLK64/SETLK64/SETLKW64
130using flock64_i386 = flock_t<int64_t>;
131
132// 64-bit `off_t` structure which is used on 64-bit targets with flock()
133struct flock64 {
134 short l_type;
135 short l_whence;
136 uint64_t l_start;
137 uint64_t l_len;
138 pid_t l_pid;
139};
140
141} // end ns kernel
142
144 /* all flock related operations provide input, so unconditionally
145 * process it */
146
147 /*
148 * The situation with fcntl() and fcntl64() is quite messy. fcntl64()
149 * only exists on 32-bit platforms like I386. The difference is only
150 * in the data structure used with the GETLK and SETLK operations.
151 *
152 * On 64-bit targets, with the single `fcntl()` system call these
153 * operations always take `struct flock` with 64-bit wide `off_t`. On
154 * i386 `fcntl()` takes `struct flock` with 32-bit wide `off_t`
155 * fields. `fcntl64` on the other hand can support both, the 32-bit
156 * wide and 64-bit wide `off_t` fields. To differentiate this,
157 * additional operations GETLK64, SETLK64 and SETLKW64 have been
158 * introduced. These operations are _only_ supported in `fcntl64` on
159 * 32-bit targets.
160 *
161 * To make things worse, the definition of F_GETLK and F_GETLK64 we
162 * get from the userspace headers depends on the following factors:
163 *
164 * - in a 64-bit compilation environment they're both always
165 * set to 5,6,7, because `fcntl()` always uses struct
166 * flock64 with 64-bit wide `off_t.
167 * - in a 32-bit compilation environment with
168 * _FILE_OFFSET_BITS=64 they're always set to 12,13,14 so
169 * that fcntl64 with struct flock64 is always used transparently.
170 * - in a 32-bit compilation environment _without_
171 * _FILE_OFFSET_BITS=64 F_GETLK & friends are set to 5,6,7
172 * and F_GETLK64 & friends are set to 12,13,14 allowing to
173 * call regular fcntl() with 32-bit wide `off_t` and fcntl64() with
174 * 32-bit or 64-bit `off_t`.
175 *
176 * Thus these constants are a moving target depending on a lot
177 * of factors, which gets even more worse when a 64-bit tracer
178 * is looking at a 32-bit emulation binary. For this reason we are
179 * using our own literal values in FcntlOperation::Oper for the LK
180 * family of operations.
181 *
182 * The rule of thumb when looking at the raw system call is that on
183 * 64-bit targets either LK operation is always expecting a 64-bit
184 * `off_t` struct. On a 32-bit target like I386 the literal values
185 * 5,6,7 are always expecting a 32-bit `off_t` and the literal values
186 * 12,13,14 are always expecting a 64-bit `off_t` (and are only
187 * supported in `fcntl64()`).
188 *
189 * The OFD_ locks are not affected by this, they always use a 64-bit
190 * wide `off_t` in `struct flock`.
191 */
192
193 cosmos::DeferGuard reset_lock{[this]() { m_lock.reset(); }};
194
195 auto assign_data = [this, &reset_lock](const auto &lock) {
196 if (!m_lock) {
197 m_lock = std::make_optional<cosmos::FileLock>(cosmos::FileLock::Type::READ_LOCK);
198 }
199
200 m_lock->l_type = lock.l_type;
201 m_lock->l_whence = lock.l_whence;
202 m_lock->l_start = lock.l_start;
203 m_lock->l_len = lock.l_len;
204 m_lock->l_pid = lock.l_pid;
205 reset_lock.disarm();
206 };
207
208 auto fetch_lock = [this, assign_data, &proc]<typename FLOCK>() {
209 FLOCK lock;
210 if (proc.readStruct(asPtr(), lock)) {
211 assign_data(lock);
212 }
213 };
214
215 if (const auto abi = m_call->abi(); abi != ABI::I386) {
216 /*
217 * if the target ABI is 64-bit based then it's easy, simply
218 * use the only supported native 64-bit struct
219 */
220 fetch_lock.operator()<kernel::flock64>();
221 return;
222 }
223
224 // on i386 things get complicated
225
226 if (const auto sysnr = m_call->callNr(); sysnr != SystemCallNr::FCNTL64) {
227 /*
228 * for the old fcntl() call only the 32-bit `off_t` structure
229 * is supported. This one also has no alignment issues if the
230 * target is a 32-bit emulation binary.
231 */
232 fetch_lock.operator()<kernel::flock32>();
233 return;
234 }
235
236 /*
237 * for the fcntl64() call both 32-bit and 64-bit `off_t` are supported
238 * depending on the value found in the `op` parameter. Also we need to
239 * consider alignment for flock64 in case we're dealing with an
240 * emulation binary.
241 */
242 auto fcntl_sys = dynamic_cast<const FcntlSystemCall*>(m_call);
243 if (!fcntl_sys)
244 // alien system call?
245 return;
246
247 if (fcntl_sys->operation.isLock64()) {
248 // either native 32-bit tracing or the target is a 32-bit
249 // emulation binary, we need i386 alignment in both cases
250 fetch_lock.operator()<kernel::flock64_i386>();
251 } else {
252 /*
253 * with the 32-bit flock no alignment issues should occur when
254 * tracing emulation binaries
255 */
256 fetch_lock.operator()<kernel::flock32>();
257 }
258}
259
261 const auto fcntl_sc = dynamic_cast<const FcntlSystemCall*>(m_call);
262 if (!fcntl_sc || fcntl_sc->operation.operation() != item::FcntlOperation::Oper::GETLK) {
263 // only the F_GETLK operation causes changes on output
264 return;
265 }
266
267 processValue(proc);
268}
269
270template <typename INT>
271static std::string_view lock_type_to_str(INT type) {
272 switch (type) {
273 CASE_ENUM_TO_STR(F_RDLCK);
274 CASE_ENUM_TO_STR(F_WRLCK);
275 CASE_ENUM_TO_STR(F_UNLCK);
276 default: return "F_???";
277 }
278}
279
280std::string FLockParameter::str() const {
281 if (!m_lock) {
282 return "<invalid>";
283 }
284
285 auto whence_str = [](short whence) {
286 switch (whence) {
287 CASE_ENUM_TO_STR(SEEK_SET);
288 CASE_ENUM_TO_STR(SEEK_CUR);
289 CASE_ENUM_TO_STR(SEEK_END);
290 default: return "???";
291 }
292 };
293
294 return cosmos::sprintf("{l_type=%s, l_whence=%s, l_start=%jd, l_len=%jd, l_pid=%d}",
295 lock_type_to_str(cosmos::to_integral(m_lock->type())).data(),
296 whence_str(cosmos::to_integral(m_lock->whence())),
297 m_lock->start(),
298 m_lock->start(),
299 cosmos::to_integral(m_lock->pid())
300 );
301}
302
304 const auto pid_or_pgid = valueAs<int>();
305
306 if (pid_or_pgid >= 0) {
307 m_pgid.reset();
308 m_pid = cosmos::ProcessID{pid_or_pgid};
309 m_short_name = "pid";
310 m_long_name = "process id";
311 } else {
312 m_pid.reset();
313 m_pgid = cosmos::ProcessGroupID{-pid_or_pgid};
314 m_short_name = "pgid";
315 m_long_name = "process group id";
316 }
317}
318
319std::string FileDescOwner::str() const {
320 if (m_pid)
321 return std::to_string(cosmos::to_integral(*m_pid));
322 else
323 return std::to_string(cosmos::to_integral(*m_pgid));
324}
325
326std::string ExtFileDescOwner::str() const {
327 if (!m_owner) {
328 return "<invalid>";
329 }
330
331 auto type_str = [](cosmos::FileDescriptor::Owner::Type type) {
332 switch (cosmos::to_integral(type)) {
333 CASE_ENUM_TO_STR(F_OWNER_TID);
334 CASE_ENUM_TO_STR(F_OWNER_PID);
335 CASE_ENUM_TO_STR(F_OWNER_PGRP);
336 default: return "???";
337 }
338 };
339
340 std::stringstream ss;
341 ss << "{type=" << type_str(m_owner->type()) << ", id=" << m_owner->raw()->pid << "}";
342 return ss.str();
343}
344
346 m_owner = cosmos::FileDescriptor::Owner{};
347
348 if (!proc.readStruct(asPtr(), *m_owner->raw())) {
349 m_owner.reset();
350 }
351}
352
354 m_lease = cosmos::FileDescriptor::LeaseType{valueAs<int>()};
355}
356
357std::string LeaseType::str() const {
358 return std::string{lock_type_to_str(cosmos::to_integral(m_lease))};
359}
360
362 m_settings = Settings{valueAs<int>()};
363}
364
365std::string DNotifySettings::str() const {
366 BITFLAGS_FORMAT_START(m_settings);
367
368 BITFLAGS_ADD(DN_ACCESS);
369 BITFLAGS_ADD(DN_MODIFY);
370 BITFLAGS_ADD(DN_CREATE);
371 BITFLAGS_ADD(DN_DELETE);
372 BITFLAGS_ADD(DN_RENAME);
373 BITFLAGS_ADD(DN_ATTRIB);
374 BITFLAGS_ADD(DN_MULTISHOT);
375
376 return BITFLAGS_STR();
377}
378
380 m_flags = cosmos::FileDescriptor::SealFlags{valueAs<unsigned int>()};
381}
382
383std::string FileSealSettings::str() const {
384 BITFLAGS_FORMAT_START(m_flags);
385
386 BITFLAGS_ADD(F_SEAL_SEAL);
387 BITFLAGS_ADD(F_SEAL_SHRINK);
388 BITFLAGS_ADD(F_SEAL_GROW);
389 BITFLAGS_ADD(F_SEAL_WRITE);
390 BITFLAGS_ADD(F_SEAL_FUTURE_WRITE);
391
392 return BITFLAGS_STR();
393}
394
396 /*
397 * this is used for both input and output parameter variants
398 */
399 uint64_t native_hint;
400 if (proc.readStruct(asPtr(), native_hint)) {
401 m_hint = Hint{native_hint};
402 } else {
403 m_hint = Hint::LIFE_NOT_SET;
404 }
405}
406
407std::string ReadWriteHint::str() const {
408 switch (cosmos::to_integral(m_hint)) {
409 CASE_ENUM_TO_STR(RWH_WRITE_LIFE_NOT_SET);
410 CASE_ENUM_TO_STR(RWH_WRITE_LIFE_NONE);
411 CASE_ENUM_TO_STR(RWH_WRITE_LIFE_SHORT);
412 CASE_ENUM_TO_STR(RWH_WRITE_LIFE_MEDIUM);
413 CASE_ENUM_TO_STR(RWH_WRITE_LIFE_LONG);
414 CASE_ENUM_TO_STR(RWH_WRITE_LIFE_EXTREME);
415 default: return "???";
416 }
417}
418
419} // end ns
const SystemCall * m_call
The system call context this item part of.
std::string_view m_short_name
A human readable short name for the item, should be one word only.
OTHER valueAs() const
Helper to cast the strongly typed Word m_val to other strong enum types.
std::string_view m_long_name
A human readable longer name for the item.
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
std::string str() const override
Returns a human readable string representation of the item.
Definition fcntl.cxx:365
void processValue(const Tracee &proc) override
Processes the value stored in m_val acc. to the actual item type.
Definition fcntl.cxx:361
void processValue(const Tracee &proc) override
Processes the value stored in m_val acc. to the actual item type.
Definition fcntl.cxx:345
std::string str() const override
Returns a human readable string representation of the item.
Definition fcntl.cxx:326
const std::optional< cosmos::FileLock > & lock() const
Access to the extracted FileLock data.
Definition fcntl.hxx:132
void processValue(const Tracee &proc) override
Processes the value stored in m_val acc. to the actual item type.
Definition fcntl.cxx:143
void updateData(const Tracee &proc) override
Called upon exit of the system call to update possible out parameters.
Definition fcntl.cxx:260
std::string str() const override
Returns a human readable string representation of the item.
Definition fcntl.cxx:280
void processValue(const Tracee &proc) override
Processes the value stored in m_val acc. to the actual item type.
Definition fcntl.cxx:29
std::string str() const override
Returns a human readable string representation of the item.
Definition fcntl.cxx:33
std::string str() const override
Returns a human readable string representation of the item.
Definition fcntl.cxx:319
void processValue(const Tracee &proc) override
Processes the value stored in m_val acc. to the actual item type.
Definition fcntl.cxx:303
std::string str() const override
Returns a human readable string representation of the item.
Definition fcntl.cxx:383
void processValue(const Tracee &proc) override
Processes the value stored in m_val acc. to the actual item type.
Definition fcntl.cxx:379
std::string str() const override
Returns a human readable string representation of the item.
Definition fcntl.cxx:357
void processValue(const Tracee &proc) override
Processes the value stored in m_val acc. to the actual item type.
Definition fcntl.cxx:353
void processValue(const Tracee &proc) override
Processes the value stored in m_val acc. to the actual item type.
Definition fcntl.cxx:395
std::string str() const override
Returns a human readable string representation of the item.
Definition fcntl.cxx:407
std::string str() const override
Returns a human readable string representation of the item.
Definition fcntl.cxx:17
void processValue(const Tracee &proc) override
Processes the value stored in m_val acc. to the actual item type.
Definition fcntl.cxx:25