1 /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
2
3 /*
4 * mountsnoop Trace mount and umount[2] syscalls
5 *
6 * Copyright (c) 2021 Hengqi Chen
7 * 30-May-2021 Hengqi Chen Created this.
8 */
9 #ifndef _GNU_SOURCE
10 #define _GNU_SOURCE
11 #endif
12 #include <argp.h>
13 #include <errno.h>
14 #include <signal.h>
15 #include <string.h>
16 #include <time.h>
17
18 #include <bpf/libbpf.h>
19 #include <bpf/bpf.h>
20 #include "mountsnoop.h"
21 #include "mountsnoop.skel.h"
22 #include "trace_helpers.h"
23
24 #define PERF_BUFFER_PAGES 64
25 #define PERF_POLL_TIMEOUT_MS 100
26 #define warn(...) fprintf(stderr, __VA_ARGS__)
27
28 /* https://www.gnu.org/software/gnulib/manual/html_node/strerrorname_005fnp.html */
29 #if !defined(__GLIBC__) || __GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 32)
strerrorname_np(int errnum)30 const char *strerrorname_np(int errnum)
31 {
32 return NULL;
33 }
34 #endif
35
36 static volatile sig_atomic_t exiting = 0;
37
38 static pid_t target_pid = 0;
39 static bool emit_timestamp = false;
40 static bool output_vertically = false;
41 static bool verbose = false;
42 static const char *flag_names[] = {
43 [0] = "MS_RDONLY",
44 [1] = "MS_NOSUID",
45 [2] = "MS_NODEV",
46 [3] = "MS_NOEXEC",
47 [4] = "MS_SYNCHRONOUS",
48 [5] = "MS_REMOUNT",
49 [6] = "MS_MANDLOCK",
50 [7] = "MS_DIRSYNC",
51 [8] = "MS_NOSYMFOLLOW",
52 [9] = "MS_NOATIME",
53 [10] = "MS_NODIRATIME",
54 [11] = "MS_BIND",
55 [12] = "MS_MOVE",
56 [13] = "MS_REC",
57 [14] = "MS_VERBOSE",
58 [15] = "MS_SILENT",
59 [16] = "MS_POSIXACL",
60 [17] = "MS_UNBINDABLE",
61 [18] = "MS_PRIVATE",
62 [19] = "MS_SLAVE",
63 [20] = "MS_SHARED",
64 [21] = "MS_RELATIME",
65 [22] = "MS_KERNMOUNT",
66 [23] = "MS_I_VERSION",
67 [24] = "MS_STRICTATIME",
68 [25] = "MS_LAZYTIME",
69 [26] = "MS_SUBMOUNT",
70 [27] = "MS_NOREMOTELOCK",
71 [28] = "MS_NOSEC",
72 [29] = "MS_BORN",
73 [30] = "MS_ACTIVE",
74 [31] = "MS_NOUSER",
75 };
76 static const int flag_count = sizeof(flag_names) / sizeof(flag_names[0]);
77
78 const char *argp_program_version = "mountsnoop 0.1";
79 const char *argp_program_bug_address =
80 "https://github.com/iovisor/bcc/tree/master/libbpf-tools";
81 const char argp_program_doc[] =
82 "Trace mount and umount syscalls.\n"
83 "\n"
84 "USAGE: mountsnoop [-h] [-t] [-p PID] [-v]\n"
85 "\n"
86 "EXAMPLES:\n"
87 " mountsnoop # trace mount and umount syscalls\n"
88 " mountsnoop -d # detailed output (one line per column value)\n"
89 " mountsnoop -p 1216 # only trace PID 1216\n";
90
91 static const struct argp_option opts[] = {
92 { "pid", 'p', "PID", 0, "Process ID to trace" },
93 { "timestamp", 't', NULL, 0, "Include timestamp on output" },
94 { "detailed", 'd', NULL, 0, "Output result in detail mode" },
95 { "verbose", 'v', NULL, 0, "Verbose debug output" },
96 { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
97 {},
98 };
99
parse_arg(int key,char * arg,struct argp_state * state)100 static error_t parse_arg(int key, char *arg, struct argp_state *state)
101 {
102 long pid;
103
104 switch (key) {
105 case 'p':
106 errno = 0;
107 pid = strtol(arg, NULL, 10);
108 if (errno || pid <= 0) {
109 warn("Invalid PID: %s\n", arg);
110 argp_usage(state);
111 }
112 target_pid = pid;
113 break;
114 case 't':
115 emit_timestamp = true;
116 break;
117 case 'd':
118 output_vertically = true;
119 break;
120 case 'v':
121 verbose = true;
122 break;
123 case 'h':
124 argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
125 break;
126 default:
127 return ARGP_ERR_UNKNOWN;
128 }
129 return 0;
130 }
131
libbpf_print_fn(enum libbpf_print_level level,const char * format,va_list args)132 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
133 {
134 if (level == LIBBPF_DEBUG && !verbose)
135 return 0;
136 return vfprintf(stderr, format, args);
137 }
138
sig_int(int signo)139 static void sig_int(int signo)
140 {
141 exiting = 1;
142 }
143
strflags(__u64 flags)144 static const char *strflags(__u64 flags)
145 {
146 static char str[512];
147 int i;
148
149 if (!flags)
150 return "0x0";
151
152 str[0] = '\0';
153 for (i = 0; i < flag_count; i++) {
154 if (!((1 << i) & flags))
155 continue;
156 if (str[0])
157 strcat(str, " | ");
158 strcat(str, flag_names[i]);
159 }
160 return str;
161 }
162
strerrno(int errnum)163 static const char *strerrno(int errnum)
164 {
165 const char *errstr;
166 static char ret[32] = {};
167
168 if (!errnum)
169 return "0";
170
171 ret[0] = '\0';
172 errstr = strerrorname_np(-errnum);
173 if (!errstr) {
174 snprintf(ret, sizeof(ret), "%d", errnum);
175 return ret;
176 }
177
178 snprintf(ret, sizeof(ret), "-%s", errstr);
179 return ret;
180 }
181
gen_call(const struct event * e)182 static const char *gen_call(const struct event *e)
183 {
184 static char call[10240];
185
186 memset(call, 0, sizeof(call));
187 if (e->op == UMOUNT) {
188 snprintf(call, sizeof(call), "umount(\"%s\", %s) = %s",
189 e->dest, strflags(e->flags), strerrno(e->ret));
190 } else {
191 snprintf(call, sizeof(call), "mount(\"%s\", \"%s\", \"%s\", %s, \"%s\") = %s",
192 e->src, e->dest, e->fs, strflags(e->flags), e->data, strerrno(e->ret));
193 }
194 return call;
195 }
196
handle_event(void * ctx,int cpu,void * data,__u32 data_sz)197 static void handle_event(void *ctx, int cpu, void *data, __u32 data_sz)
198 {
199 const struct event *e = data;
200 struct tm *tm;
201 char ts[32];
202 time_t t;
203 const char *indent;
204 static const char *op_name[] = {
205 [MOUNT] = "MOUNT",
206 [UMOUNT] = "UMOUNT",
207 };
208
209 if (emit_timestamp) {
210 time(&t);
211 tm = localtime(&t);
212 strftime(ts, sizeof(ts), "%H:%M:%S ", tm);
213 printf("%s", ts);
214 indent = " ";
215 } else {
216 indent = "";
217 }
218 if (!output_vertically) {
219 printf("%-16s %-7d %-7d %-11u %s\n",
220 e->comm, e->pid, e->tid, e->mnt_ns, gen_call(e));
221 return;
222 }
223 if (emit_timestamp)
224 printf("\n");
225 printf("%sPID: %d\n", indent, e->pid);
226 printf("%sTID: %d\n", indent, e->tid);
227 printf("%sCOMM: %s\n", indent, e->comm);
228 printf("%sOP: %s\n", indent, op_name[e->op]);
229 printf("%sRET: %s\n", indent, strerrno(e->ret));
230 printf("%sLAT: %lldus\n", indent, e->delta / 1000);
231 printf("%sMNT_NS: %u\n", indent, e->mnt_ns);
232 printf("%sFS: %s\n", indent, e->fs);
233 printf("%sSOURCE: %s\n", indent, e->src);
234 printf("%sTARGET: %s\n", indent, e->dest);
235 printf("%sDATA: %s\n", indent, e->data);
236 printf("%sFLAGS: %s\n", indent, strflags(e->flags));
237 printf("\n");
238 }
239
handle_lost_events(void * ctx,int cpu,__u64 lost_cnt)240 static void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt)
241 {
242 warn("lost %llu events on CPU #%d\n", lost_cnt, cpu);
243 }
244
main(int argc,char ** argv)245 int main(int argc, char **argv)
246 {
247 static const struct argp argp = {
248 .options = opts,
249 .parser = parse_arg,
250 .doc = argp_program_doc,
251 };
252 struct perf_buffer *pb = NULL;
253 struct mountsnoop_bpf *obj;
254 int err;
255
256 err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
257 if (err)
258 return err;
259
260 libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
261 libbpf_set_print(libbpf_print_fn);
262
263 obj = mountsnoop_bpf__open();
264 if (!obj) {
265 warn("failed to open BPF object\n");
266 return 1;
267 }
268
269 obj->rodata->target_pid = target_pid;
270
271 err = mountsnoop_bpf__load(obj);
272 if (err) {
273 warn("failed to load BPF object: %d\n", err);
274 goto cleanup;
275 }
276
277 err = mountsnoop_bpf__attach(obj);
278 if (err) {
279 warn("failed to attach BPF programs: %d\n", err);
280 goto cleanup;
281 }
282
283 pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES,
284 handle_event, handle_lost_events, NULL, NULL);
285 if (!pb) {
286 err = -errno;
287 warn("failed to open perf buffer: %d\n", err);
288 goto cleanup;
289 }
290
291 if (signal(SIGINT, sig_int) == SIG_ERR) {
292 warn("can't set signal handler: %s\n", strerror(errno));
293 err = 1;
294 goto cleanup;
295 }
296
297 if (!output_vertically) {
298 if (emit_timestamp)
299 printf("%-8s ", "TIME");
300 printf("%-16s %-7s %-7s %-11s %s\n", "COMM", "PID", "TID", "MNT_NS", "CALL");
301 }
302
303 while (!exiting) {
304 err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
305 if (err < 0 && err != -EINTR) {
306 fprintf(stderr, "error polling perf buffer: %s\n", strerror(-err));
307 goto cleanup;
308 }
309 /* reset err to return 0 if exiting */
310 err = 0;
311 }
312
313 cleanup:
314 perf_buffer__free(pb);
315 mountsnoop_bpf__destroy(obj);
316
317 return err != 0;
318 }
319