Commit | Line | Data |
---|---|---|
a09e091a JB |
1 | /* |
2 | * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All | |
3 | * rights reserved. | |
4 | * Copyright (c) 1993, 2010, Oracle and/or its affiliates. All rights reserved. | |
5 | * | |
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
7 | * of this software and associated documentation files (the "Software"), to deal | |
8 | * in the Software without restriction, including without limitation the rights | |
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
10 | * copies of the Software, and to permit persons to whom the Software is | |
11 | * furnished to do so, subject to the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice shall be included in | |
14 | * all copies or substantial portions of the Software. | |
15 | * | |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
22 | * THE SOFTWARE. | |
23 | */ | |
24 | ||
25 | /** | |
26 | * @file | |
27 | * | |
28 | * This file contains functionality for identifying clients by various | |
29 | * means. The primary purpose of identification is to simply aid in | |
30 | * finding out which clients are using X server and how they are using | |
31 | * it. For example, it's often necessary to monitor what requests | |
32 | * clients are executing (to spot bad behaviour) and how they are | |
33 | * allocating resources in X server (to spot excessive resource | |
34 | * usage). | |
35 | * | |
36 | * This framework automatically allocates information, that can be | |
37 | * used for client identification, when a client connects to the | |
38 | * server. The information is freed when the client disconnects. The | |
39 | * allocated information is just a collection of various IDs, such as | |
40 | * PID and process name for local clients, that are likely to be | |
41 | * useful in analyzing X server usage. | |
42 | * | |
43 | * Users of the framework can query ID information about clients at | |
44 | * any time. To avoid repeated polling of IDs the users can also | |
45 | * subscribe for notifications about the availability of ID | |
46 | * information. IDs have been allocated before ClientStateCallback is | |
47 | * called with ClientStateInitial state. Similarly the IDs will be | |
48 | * released after ClientStateCallback is called with ClientStateGone | |
49 | * state. | |
50 | * | |
51 | * Author: Rami Ylimäki <rami.ylimaki@vincit.fi> | |
52 | */ | |
53 | ||
54 | #include <sys/stat.h> | |
55 | #include <fcntl.h> | |
56 | #include <unistd.h> | |
57 | ||
58 | #include "client.h" | |
59 | #include "os.h" | |
60 | #include "dixstruct.h" | |
61 | ||
62 | #ifdef __sun | |
63 | #include <errno.h> | |
64 | #include <procfs.h> | |
65 | #endif | |
66 | ||
67 | #ifdef __OpenBSD__ | |
68 | #include <sys/param.h> | |
69 | #include <sys/sysctl.h> | |
70 | #include <sys/types.h> | |
71 | ||
72 | #include <kvm.h> | |
73 | #include <limits.h> | |
74 | #endif | |
75 | ||
76 | /** | |
77 | * Try to determine a PID for a client from its connection | |
78 | * information. This should be called only once when new client has | |
79 | * connected, use GetClientPid to determine the PID at other times. | |
80 | * | |
81 | * @param[in] client Connection linked to some process. | |
82 | * | |
83 | * @return PID of the client. Error (-1) if PID can't be determined | |
84 | * for the client. | |
85 | * | |
86 | * @see GetClientPid | |
87 | */ | |
88 | pid_t | |
89 | DetermineClientPid(struct _Client * client) | |
90 | { | |
91 | LocalClientCredRec *lcc = NULL; | |
92 | pid_t pid = -1; | |
93 | ||
94 | if (client == NullClient) | |
95 | return pid; | |
96 | ||
97 | if (client == serverClient) | |
98 | return getpid(); | |
99 | ||
100 | if (GetLocalClientCreds(client, &lcc) != -1) { | |
101 | if (lcc->fieldsSet & LCC_PID_SET) | |
102 | pid = lcc->pid; | |
103 | FreeLocalClientCreds(lcc); | |
104 | } | |
105 | ||
106 | return pid; | |
107 | } | |
108 | ||
109 | /** | |
110 | * Try to determine a command line string for a client based on its | |
111 | * PID. Note that mapping PID to a command hasn't been implemented for | |
112 | * some operating systems. This should be called only once when a new | |
113 | * client has connected, use GetClientCmdName/Args to determine the | |
114 | * string at other times. | |
115 | * | |
116 | * @param[in] pid Process ID of a client. | |
117 | ||
118 | * @param[out] cmdname Client process name without arguments. You must | |
119 | * release this by calling free. On error NULL is | |
120 | * returned. Pass NULL if you aren't interested in | |
121 | * this value. | |
122 | * @param[out] cmdargs Arguments to client process. Useful for | |
123 | * identifying a client that is executed from a | |
124 | * launcher program. You must release this by | |
125 | * calling free. On error NULL is returned. Pass | |
126 | * NULL if you aren't interested in this value. | |
127 | * | |
128 | * @see GetClientCmdName/Args | |
129 | */ | |
130 | void | |
131 | DetermineClientCmd(pid_t pid, const char **cmdname, const char **cmdargs) | |
132 | { | |
133 | char path[PATH_MAX + 1]; | |
134 | int totsize = 0; | |
135 | int fd = 0; | |
136 | ||
137 | if (cmdname) | |
138 | *cmdname = NULL; | |
139 | if (cmdargs) | |
140 | *cmdargs = NULL; | |
141 | ||
142 | if (pid == -1) | |
143 | return; | |
144 | ||
145 | #ifdef __sun /* Solaris */ | |
146 | /* Solaris does not support /proc/pid/cmdline, but makes information | |
147 | * similar to what ps shows available in a binary structure in the | |
148 | * /proc/pid/psinfo file. */ | |
149 | if (snprintf(path, sizeof(path), "/proc/%d/psinfo", pid) < 0) | |
150 | return; | |
151 | fd = open(path, O_RDONLY); | |
152 | if (fd < 0) { | |
153 | ErrorF("Failed to open %s: %s\n", path, strerror(errno)); | |
154 | return; | |
155 | } | |
156 | else { | |
157 | psinfo_t psinfo = { 0 }; | |
158 | char *sp; | |
159 | ||
160 | totsize = read(fd, &psinfo, sizeof(psinfo_t)); | |
161 | close(fd); | |
162 | if (totsize <= 0) | |
163 | return; | |
164 | ||
165 | /* pr_psargs is the first PRARGSZ (80) characters of the command | |
166 | * line string - assume up to the first space is the command name, | |
167 | * since it's not delimited. While there is also pr_fname, that's | |
168 | * more limited, giving only the first 16 chars of the basename of | |
169 | * the file that was exec'ed, thus cutting off many long gnome | |
170 | * command names, or returning "isapython2.6" for all python scripts. | |
171 | */ | |
172 | psinfo.pr_psargs[PRARGSZ - 1] = '\0'; | |
173 | sp = strchr(psinfo.pr_psargs, ' '); | |
174 | if (sp) | |
175 | *sp++ = '\0'; | |
176 | ||
177 | if (cmdname) | |
178 | *cmdname = strdup(psinfo.pr_psargs); | |
179 | ||
180 | if (cmdargs && sp) | |
181 | *cmdargs = strdup(sp); | |
182 | } | |
183 | #elif defined(__OpenBSD__) | |
184 | /* on OpenBSD use kvm_getargv() */ | |
185 | { | |
186 | kvm_t *kd; | |
187 | char errbuf[_POSIX2_LINE_MAX]; | |
188 | char **argv; | |
189 | struct kinfo_proc *kp; | |
190 | size_t len = 0; | |
191 | int i, n; | |
192 | ||
193 | kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, errbuf); | |
194 | if (kd == NULL) | |
195 | return; | |
196 | kp = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(struct kinfo_proc), | |
197 | &n); | |
198 | if (n != 1) | |
199 | return; | |
200 | argv = kvm_getargv(kd, kp, 0); | |
201 | *cmdname = strdup(argv[0]); | |
202 | i = 1; | |
203 | while (argv[i] != NULL) { | |
204 | len += strlen(argv[i]) + 1; | |
205 | i++; | |
206 | } | |
207 | *cmdargs = calloc(1, len); | |
208 | i = 1; | |
209 | while (argv[i] != NULL) { | |
210 | strlcat(*cmdargs, argv[i], len); | |
211 | strlcat(*cmdargs, " ", len); | |
212 | i++; | |
213 | } | |
214 | kvm_close(kd); | |
215 | } | |
216 | #else /* Linux using /proc/pid/cmdline */ | |
217 | ||
218 | /* Check if /proc/pid/cmdline exists. It's not supported on all | |
219 | * operating systems. */ | |
220 | if (snprintf(path, sizeof(path), "/proc/%d/cmdline", pid) < 0) | |
221 | return; | |
222 | fd = open(path, O_RDONLY); | |
223 | if (fd < 0) | |
224 | return; | |
225 | ||
226 | /* Read the contents of /proc/pid/cmdline. It should contain the | |
227 | * process name and arguments. */ | |
228 | totsize = read(fd, path, sizeof(path)); | |
229 | close(fd); | |
230 | if (totsize <= 0) | |
231 | return; | |
232 | path[totsize - 1] = '\0'; | |
233 | ||
234 | /* Contruct the process name without arguments. */ | |
235 | if (cmdname) { | |
236 | *cmdname = strdup(path); | |
237 | } | |
238 | ||
239 | /* Construct the arguments for client process. */ | |
240 | if (cmdargs) { | |
241 | int cmdsize = strlen(path) + 1; | |
242 | int argsize = totsize - cmdsize; | |
243 | char *args = NULL; | |
244 | ||
245 | if (argsize > 0) | |
246 | args = malloc(argsize); | |
247 | if (args) { | |
248 | int i = 0; | |
249 | ||
250 | for (i = 0; i < (argsize - 1); ++i) { | |
251 | const char c = path[cmdsize + i]; | |
252 | ||
253 | args[i] = (c == '\0') ? ' ' : c; | |
254 | } | |
255 | args[argsize - 1] = '\0'; | |
256 | *cmdargs = args; | |
257 | } | |
258 | } | |
259 | #endif | |
260 | } | |
261 | ||
262 | /** | |
263 | * Called when a new client connects. Allocates client ID information. | |
264 | * | |
265 | * @param[in] client Recently connected client. | |
266 | */ | |
267 | void | |
268 | ReserveClientIds(struct _Client *client) | |
269 | { | |
270 | #ifdef CLIENTIDS | |
271 | if (client == NullClient) | |
272 | return; | |
273 | ||
274 | assert(!client->clientIds); | |
275 | client->clientIds = calloc(1, sizeof(ClientIdRec)); | |
276 | if (!client->clientIds) | |
277 | return; | |
278 | ||
279 | client->clientIds->pid = DetermineClientPid(client); | |
280 | if (client->clientIds->pid != -1) | |
281 | DetermineClientCmd(client->clientIds->pid, &client->clientIds->cmdname, | |
282 | &client->clientIds->cmdargs); | |
283 | ||
284 | DebugF("client(%lx): Reserved pid(%d).\n", | |
285 | (unsigned long) client->clientAsMask, client->clientIds->pid); | |
286 | DebugF("client(%lx): Reserved cmdname(%s) and cmdargs(%s).\n", | |
287 | (unsigned long) client->clientAsMask, | |
288 | client->clientIds->cmdname ? client->clientIds->cmdname : "NULL", | |
289 | client->clientIds->cmdargs ? client->clientIds->cmdargs : "NULL"); | |
290 | #endif /* CLIENTIDS */ | |
291 | } | |
292 | ||
293 | /** | |
294 | * Called when an existing client disconnects. Frees client ID | |
295 | * information. | |
296 | * | |
297 | * @param[in] client Recently disconnected client. | |
298 | */ | |
299 | void | |
300 | ReleaseClientIds(struct _Client *client) | |
301 | { | |
302 | #ifdef CLIENTIDS | |
303 | if (client == NullClient) | |
304 | return; | |
305 | ||
306 | if (!client->clientIds) | |
307 | return; | |
308 | ||
309 | DebugF("client(%lx): Released pid(%d).\n", | |
310 | (unsigned long) client->clientAsMask, client->clientIds->pid); | |
311 | DebugF("client(%lx): Released cmdline(%s) and cmdargs(%s).\n", | |
312 | (unsigned long) client->clientAsMask, | |
313 | client->clientIds->cmdname ? client->clientIds->cmdname : "NULL", | |
314 | client->clientIds->cmdargs ? client->clientIds->cmdargs : "NULL"); | |
315 | ||
316 | free((void *) client->clientIds->cmdname); /* const char * */ | |
317 | free((void *) client->clientIds->cmdargs); /* const char * */ | |
318 | free(client->clientIds); | |
319 | client->clientIds = NULL; | |
320 | #endif /* CLIENTIDS */ | |
321 | } | |
322 | ||
323 | /** | |
324 | * Get cached PID of a client. | |
325 | * | |
326 | * param[in] client Client whose PID has been already cached. | |
327 | * | |
328 | * @return Cached client PID. Error (-1) if called: | |
329 | * - before ClientStateInitial client state notification | |
330 | * - after ClientStateGone client state notification | |
331 | * - for remote clients | |
332 | * | |
333 | * @see DetermineClientPid | |
334 | */ | |
335 | pid_t | |
336 | GetClientPid(struct _Client *client) | |
337 | { | |
338 | if (client == NullClient) | |
339 | return -1; | |
340 | ||
341 | if (!client->clientIds) | |
342 | return -1; | |
343 | ||
344 | return client->clientIds->pid; | |
345 | } | |
346 | ||
347 | /** | |
348 | * Get cached command name string of a client. | |
349 | * | |
350 | * param[in] client Client whose command line string has been already | |
351 | * cached. | |
352 | * | |
353 | * @return Cached client command name. Error (NULL) if called: | |
354 | * - before ClientStateInitial client state notification | |
355 | * - after ClientStateGone client state notification | |
356 | * - for remote clients | |
357 | * - on OS that doesn't support mapping of PID to command line | |
358 | * | |
359 | * @see DetermineClientCmd | |
360 | */ | |
361 | const char * | |
362 | GetClientCmdName(struct _Client *client) | |
363 | { | |
364 | if (client == NullClient) | |
365 | return NULL; | |
366 | ||
367 | if (!client->clientIds) | |
368 | return NULL; | |
369 | ||
370 | return client->clientIds->cmdname; | |
371 | } | |
372 | ||
373 | /** | |
374 | * Get cached command arguments string of a client. | |
375 | * | |
376 | * param[in] client Client whose command line string has been already | |
377 | * cached. | |
378 | * | |
379 | * @return Cached client command arguments. Error (NULL) if called: | |
380 | * - before ClientStateInitial client state notification | |
381 | * - after ClientStateGone client state notification | |
382 | * - for remote clients | |
383 | * - on OS that doesn't support mapping of PID to command line | |
384 | * | |
385 | * @see DetermineClientCmd | |
386 | */ | |
387 | const char * | |
388 | GetClientCmdArgs(struct _Client *client) | |
389 | { | |
390 | if (client == NullClient) | |
391 | return NULL; | |
392 | ||
393 | if (!client->clientIds) | |
394 | return NULL; | |
395 | ||
396 | return client->clientIds->cmdargs; | |
397 | } |