1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * |
4 | * Copyright (C) 2017 Hari Bathini, IBM Corporation |
5 | */ |
6 | |
7 | #include "namespaces.h" |
8 | #include "event.h" |
9 | #include "get_current_dir_name.h" |
10 | #include <sys/types.h> |
11 | #include <sys/stat.h> |
12 | #include <fcntl.h> |
13 | #include <limits.h> |
14 | #include <sched.h> |
15 | #include <stdlib.h> |
16 | #include <stdio.h> |
17 | #include <string.h> |
18 | #include <unistd.h> |
19 | #include <asm/bug.h> |
20 | #include <linux/kernel.h> |
21 | #include <linux/zalloc.h> |
22 | |
23 | static const char *perf_ns__names[] = { |
24 | [NET_NS_INDEX] = "net" , |
25 | [UTS_NS_INDEX] = "uts" , |
26 | [IPC_NS_INDEX] = "ipc" , |
27 | [PID_NS_INDEX] = "pid" , |
28 | [USER_NS_INDEX] = "user" , |
29 | [MNT_NS_INDEX] = "mnt" , |
30 | [CGROUP_NS_INDEX] = "cgroup" , |
31 | }; |
32 | |
33 | const char *perf_ns__name(unsigned int id) |
34 | { |
35 | if (id >= ARRAY_SIZE(perf_ns__names)) |
36 | return "UNKNOWN" ; |
37 | return perf_ns__names[id]; |
38 | } |
39 | |
40 | struct namespaces *namespaces__new(struct perf_record_namespaces *event) |
41 | { |
42 | struct namespaces *namespaces; |
43 | u64 link_info_size = ((event ? event->nr_namespaces : NR_NAMESPACES) * |
44 | sizeof(struct perf_ns_link_info)); |
45 | |
46 | namespaces = zalloc(sizeof(struct namespaces) + link_info_size); |
47 | if (!namespaces) |
48 | return NULL; |
49 | |
50 | namespaces->end_time = -1; |
51 | |
52 | if (event) |
53 | memcpy(namespaces->link_info, event->link_info, link_info_size); |
54 | |
55 | return namespaces; |
56 | } |
57 | |
58 | void namespaces__free(struct namespaces *namespaces) |
59 | { |
60 | free(namespaces); |
61 | } |
62 | |
63 | static int nsinfo__get_nspid(pid_t *tgid, pid_t *nstgid, bool *in_pidns, const char *path) |
64 | { |
65 | FILE *f = NULL; |
66 | char *statln = NULL; |
67 | size_t linesz = 0; |
68 | char *nspid; |
69 | |
70 | f = fopen(path, "r" ); |
71 | if (f == NULL) |
72 | return -1; |
73 | |
74 | while (getline(&statln, &linesz, f) != -1) { |
75 | /* Use tgid if CONFIG_PID_NS is not defined. */ |
76 | if (strstr(statln, "Tgid:" ) != NULL) { |
77 | *tgid = (pid_t)strtol(strrchr(statln, '\t'), NULL, 10); |
78 | *nstgid = *tgid; |
79 | } |
80 | |
81 | if (strstr(statln, "NStgid:" ) != NULL) { |
82 | nspid = strrchr(statln, '\t'); |
83 | *nstgid = (pid_t)strtol(nspid, NULL, 10); |
84 | /* |
85 | * If innermost tgid is not the first, process is in a different |
86 | * PID namespace. |
87 | */ |
88 | *in_pidns = (statln + sizeof("NStgid:" ) - 1) != nspid; |
89 | break; |
90 | } |
91 | } |
92 | |
93 | fclose(f); |
94 | free(statln); |
95 | return 0; |
96 | } |
97 | |
98 | int nsinfo__init(struct nsinfo *nsi) |
99 | { |
100 | char oldns[PATH_MAX]; |
101 | char spath[PATH_MAX]; |
102 | char *newns = NULL; |
103 | struct stat old_stat; |
104 | struct stat new_stat; |
105 | int rv = -1; |
106 | |
107 | if (snprintf(buf: oldns, PATH_MAX, fmt: "/proc/self/ns/mnt" ) >= PATH_MAX) |
108 | return rv; |
109 | |
110 | if (asprintf(&newns, "/proc/%d/ns/mnt" , nsinfo__pid(nsi)) == -1) |
111 | return rv; |
112 | |
113 | if (stat(oldns, &old_stat) < 0) |
114 | goto out; |
115 | |
116 | if (stat(newns, &new_stat) < 0) |
117 | goto out; |
118 | |
119 | /* Check if the mount namespaces differ, if so then indicate that we |
120 | * want to switch as part of looking up dso/map data. |
121 | */ |
122 | if (old_stat.st_ino != new_stat.st_ino) { |
123 | RC_CHK_ACCESS(nsi)->need_setns = true; |
124 | RC_CHK_ACCESS(nsi)->mntns_path = newns; |
125 | newns = NULL; |
126 | } |
127 | |
128 | /* If we're dealing with a process that is in a different PID namespace, |
129 | * attempt to work out the innermost tgid for the process. |
130 | */ |
131 | if (snprintf(buf: spath, PATH_MAX, fmt: "/proc/%d/status" , nsinfo__pid(nsi)) >= PATH_MAX) |
132 | goto out; |
133 | |
134 | rv = nsinfo__get_nspid(tgid: &RC_CHK_ACCESS(nsi)->tgid, nstgid: &RC_CHK_ACCESS(nsi)->nstgid, |
135 | in_pidns: &RC_CHK_ACCESS(nsi)->in_pidns, path: spath); |
136 | |
137 | out: |
138 | free(newns); |
139 | return rv; |
140 | } |
141 | |
142 | static struct nsinfo *nsinfo__alloc(void) |
143 | { |
144 | struct nsinfo *res; |
145 | RC_STRUCT(nsinfo) *nsi; |
146 | |
147 | nsi = calloc(1, sizeof(*nsi)); |
148 | if (ADD_RC_CHK(res, nsi)) |
149 | refcount_set(r: &nsi->refcnt, n: 1); |
150 | |
151 | return res; |
152 | } |
153 | |
154 | struct nsinfo *nsinfo__new(pid_t pid) |
155 | { |
156 | struct nsinfo *nsi; |
157 | |
158 | if (pid == 0) |
159 | return NULL; |
160 | |
161 | nsi = nsinfo__alloc(); |
162 | if (!nsi) |
163 | return NULL; |
164 | |
165 | RC_CHK_ACCESS(nsi)->pid = pid; |
166 | RC_CHK_ACCESS(nsi)->tgid = pid; |
167 | RC_CHK_ACCESS(nsi)->nstgid = pid; |
168 | nsinfo__clear_need_setns(nsi); |
169 | RC_CHK_ACCESS(nsi)->in_pidns = false; |
170 | /* Init may fail if the process exits while we're trying to look at its |
171 | * proc information. In that case, save the pid but don't try to enter |
172 | * the namespace. |
173 | */ |
174 | if (nsinfo__init(nsi) == -1) |
175 | nsinfo__clear_need_setns(nsi); |
176 | |
177 | return nsi; |
178 | } |
179 | |
180 | static const char *nsinfo__mntns_path(const struct nsinfo *nsi) |
181 | { |
182 | return RC_CHK_ACCESS(nsi)->mntns_path; |
183 | } |
184 | |
185 | struct nsinfo *nsinfo__copy(const struct nsinfo *nsi) |
186 | { |
187 | struct nsinfo *nnsi; |
188 | |
189 | if (nsi == NULL) |
190 | return NULL; |
191 | |
192 | nnsi = nsinfo__alloc(); |
193 | if (!nnsi) |
194 | return NULL; |
195 | |
196 | RC_CHK_ACCESS(nnsi)->pid = nsinfo__pid(nsi); |
197 | RC_CHK_ACCESS(nnsi)->tgid = nsinfo__tgid(nsi); |
198 | RC_CHK_ACCESS(nnsi)->nstgid = nsinfo__nstgid(nsi); |
199 | RC_CHK_ACCESS(nnsi)->need_setns = nsinfo__need_setns(nsi); |
200 | RC_CHK_ACCESS(nnsi)->in_pidns = nsinfo__in_pidns(nsi); |
201 | if (nsinfo__mntns_path(nsi)) { |
202 | RC_CHK_ACCESS(nnsi)->mntns_path = strdup(nsinfo__mntns_path(nsi)); |
203 | if (!RC_CHK_ACCESS(nnsi)->mntns_path) { |
204 | nsinfo__put(nsi: nnsi); |
205 | return NULL; |
206 | } |
207 | } |
208 | |
209 | return nnsi; |
210 | } |
211 | |
212 | static refcount_t *nsinfo__refcnt(struct nsinfo *nsi) |
213 | { |
214 | return &RC_CHK_ACCESS(nsi)->refcnt; |
215 | } |
216 | |
217 | static void nsinfo__delete(struct nsinfo *nsi) |
218 | { |
219 | if (nsi) { |
220 | WARN_ONCE(refcount_read(nsinfo__refcnt(nsi)) != 0, "nsinfo refcnt unbalanced\n" ); |
221 | zfree(&RC_CHK_ACCESS(nsi)->mntns_path); |
222 | RC_CHK_FREE(nsi); |
223 | } |
224 | } |
225 | |
226 | struct nsinfo *nsinfo__get(struct nsinfo *nsi) |
227 | { |
228 | struct nsinfo *result; |
229 | |
230 | if (RC_CHK_GET(result, nsi)) |
231 | refcount_inc(r: nsinfo__refcnt(nsi)); |
232 | |
233 | return result; |
234 | } |
235 | |
236 | void nsinfo__put(struct nsinfo *nsi) |
237 | { |
238 | if (nsi && refcount_dec_and_test(r: nsinfo__refcnt(nsi))) |
239 | nsinfo__delete(nsi); |
240 | else |
241 | RC_CHK_PUT(nsi); |
242 | } |
243 | |
244 | bool nsinfo__need_setns(const struct nsinfo *nsi) |
245 | { |
246 | return RC_CHK_ACCESS(nsi)->need_setns; |
247 | } |
248 | |
249 | void nsinfo__clear_need_setns(struct nsinfo *nsi) |
250 | { |
251 | RC_CHK_ACCESS(nsi)->need_setns = false; |
252 | } |
253 | |
254 | pid_t nsinfo__tgid(const struct nsinfo *nsi) |
255 | { |
256 | return RC_CHK_ACCESS(nsi)->tgid; |
257 | } |
258 | |
259 | pid_t nsinfo__nstgid(const struct nsinfo *nsi) |
260 | { |
261 | return RC_CHK_ACCESS(nsi)->nstgid; |
262 | } |
263 | |
264 | pid_t nsinfo__pid(const struct nsinfo *nsi) |
265 | { |
266 | return RC_CHK_ACCESS(nsi)->pid; |
267 | } |
268 | |
269 | pid_t nsinfo__in_pidns(const struct nsinfo *nsi) |
270 | { |
271 | return RC_CHK_ACCESS(nsi)->in_pidns; |
272 | } |
273 | |
274 | void nsinfo__mountns_enter(struct nsinfo *nsi, |
275 | struct nscookie *nc) |
276 | { |
277 | char curpath[PATH_MAX]; |
278 | int oldns = -1; |
279 | int newns = -1; |
280 | char *oldcwd = NULL; |
281 | |
282 | if (nc == NULL) |
283 | return; |
284 | |
285 | nc->oldns = -1; |
286 | nc->newns = -1; |
287 | |
288 | if (!nsi || !nsinfo__need_setns(nsi)) |
289 | return; |
290 | |
291 | if (snprintf(buf: curpath, PATH_MAX, fmt: "/proc/self/ns/mnt" ) >= PATH_MAX) |
292 | return; |
293 | |
294 | oldcwd = get_current_dir_name(); |
295 | if (!oldcwd) |
296 | return; |
297 | |
298 | oldns = open(curpath, O_RDONLY); |
299 | if (oldns < 0) |
300 | goto errout; |
301 | |
302 | newns = open(nsinfo__mntns_path(nsi), O_RDONLY); |
303 | if (newns < 0) |
304 | goto errout; |
305 | |
306 | if (setns(fd: newns, CLONE_NEWNS) < 0) |
307 | goto errout; |
308 | |
309 | nc->oldcwd = oldcwd; |
310 | nc->oldns = oldns; |
311 | nc->newns = newns; |
312 | return; |
313 | |
314 | errout: |
315 | free(oldcwd); |
316 | if (oldns > -1) |
317 | close(oldns); |
318 | if (newns > -1) |
319 | close(newns); |
320 | } |
321 | |
322 | void nsinfo__mountns_exit(struct nscookie *nc) |
323 | { |
324 | if (nc == NULL || nc->oldns == -1 || nc->newns == -1 || !nc->oldcwd) |
325 | return; |
326 | |
327 | setns(fd: nc->oldns, CLONE_NEWNS); |
328 | |
329 | if (nc->oldcwd) { |
330 | WARN_ON_ONCE(chdir(nc->oldcwd)); |
331 | zfree(&nc->oldcwd); |
332 | } |
333 | |
334 | if (nc->oldns > -1) { |
335 | close(nc->oldns); |
336 | nc->oldns = -1; |
337 | } |
338 | |
339 | if (nc->newns > -1) { |
340 | close(nc->newns); |
341 | nc->newns = -1; |
342 | } |
343 | } |
344 | |
345 | char *nsinfo__realpath(const char *path, struct nsinfo *nsi) |
346 | { |
347 | char *rpath; |
348 | struct nscookie nsc; |
349 | |
350 | nsinfo__mountns_enter(nsi, nc: &nsc); |
351 | rpath = realpath(path, NULL); |
352 | nsinfo__mountns_exit(nc: &nsc); |
353 | |
354 | return rpath; |
355 | } |
356 | |
357 | int nsinfo__stat(const char *filename, struct stat *st, struct nsinfo *nsi) |
358 | { |
359 | int ret; |
360 | struct nscookie nsc; |
361 | |
362 | nsinfo__mountns_enter(nsi, nc: &nsc); |
363 | ret = stat(filename, st); |
364 | nsinfo__mountns_exit(nc: &nsc); |
365 | |
366 | return ret; |
367 | } |
368 | |
369 | bool nsinfo__is_in_root_namespace(void) |
370 | { |
371 | pid_t tgid = 0, nstgid = 0; |
372 | bool in_pidns = false; |
373 | |
374 | nsinfo__get_nspid(tgid: &tgid, nstgid: &nstgid, in_pidns: &in_pidns, path: "/proc/self/status" ); |
375 | return !in_pidns; |
376 | } |
377 | |