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
DetermineClientPid(struct _Client * client)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
DetermineClientCmd(pid_t pid,const char ** cmdname,const char ** cmdargs)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 #if defined(__OpenBSD__)
146 /* on OpenBSD use kvm_getargv() */
147 {
148 kvm_t *kd;
149 char errbuf[_POSIX2_LINE_MAX];
150 char **argv;
151 struct kinfo_proc *kp;
152 size_t len = 0;
153 int i, n;
154
155 kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
156 if (kd == NULL)
157 return;
158 kp = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(struct kinfo_proc),
159 &n);
160 if (n != 1)
161 return;
162 argv = kvm_getargv(kd, kp, 0);
163 *cmdname = strdup(argv[0]);
164 i = 1;
165 while (argv[i] != NULL) {
166 len += strlen(argv[i]) + 1;
167 i++;
168 }
169 *cmdargs = calloc(1, len);
170 i = 1;
171 while (argv[i] != NULL) {
172 strlcat(*cmdargs, argv[i], len);
173 strlcat(*cmdargs, " ", len);
174 i++;
175 }
176 kvm_close(kd);
177 }
178 #else /* Linux using /proc/pid/cmdline */
179
180 /* Check if /proc/pid/cmdline exists. It's not supported on all
181 * operating systems. */
182 if (snprintf(path, sizeof(path), "/proc/%d/cmdline", pid) < 0)
183 return;
184 fd = open(path, O_RDONLY);
185 if (fd < 0)
186 #ifdef __sun
187 goto fallback;
188 #else
189 return;
190 #endif
191
192 /* Read the contents of /proc/pid/cmdline. It should contain the
193 * process name and arguments. */
194 totsize = read(fd, path, sizeof(path));
195 close(fd);
196 if (totsize <= 0)
197 return;
198 path[totsize - 1] = '\0';
199
200 /* Contruct the process name without arguments. */
201 if (cmdname) {
202 *cmdname = strdup(path);
203 }
204
205 /* Construct the arguments for client process. */
206 if (cmdargs) {
207 int cmdsize = strlen(path) + 1;
208 int argsize = totsize - cmdsize;
209 char *args = NULL;
210
211 if (argsize > 0)
212 args = malloc(argsize);
213 if (args) {
214 int i = 0;
215
216 for (i = 0; i < (argsize - 1); ++i) {
217 const char c = path[cmdsize + i];
218
219 args[i] = (c == '\0') ? ' ' : c;
220 }
221 args[argsize - 1] = '\0';
222 *cmdargs = args;
223 }
224 }
225 return;
226 #endif
227
228 #ifdef __sun /* Solaris */
229 fallback:
230 /* Solaris prior to 11.3.5 does not support /proc/pid/cmdline, but
231 * makes information similar to what ps shows available in a binary
232 * structure in the /proc/pid/psinfo file. */
233 if (snprintf(path, sizeof(path), "/proc/%d/psinfo", pid) < 0)
234 return;
235 fd = open(path, O_RDONLY);
236 if (fd < 0) {
237 ErrorF("Failed to open %s: %s\n", path, strerror(errno));
238 return;
239 }
240 else {
241 psinfo_t psinfo = { 0 };
242 char *sp;
243
244 totsize = read(fd, &psinfo, sizeof(psinfo_t));
245 close(fd);
246 if (totsize <= 0)
247 return;
248
249 /* pr_psargs is the first PRARGSZ (80) characters of the command
250 * line string - assume up to the first space is the command name,
251 * since it's not delimited. While there is also pr_fname, that's
252 * more limited, giving only the first 16 chars of the basename of
253 * the file that was exec'ed, thus cutting off many long gnome
254 * command names, or returning "isapython2.6" for all python scripts.
255 */
256 psinfo.pr_psargs[PRARGSZ - 1] = '\0';
257 sp = strchr(psinfo.pr_psargs, ' ');
258 if (sp)
259 *sp++ = '\0';
260
261 if (cmdname)
262 *cmdname = strdup(psinfo.pr_psargs);
263
264 if (cmdargs && sp)
265 *cmdargs = strdup(sp);
266 }
267 #endif
268 }
269
270 /**
271 * Called when a new client connects. Allocates client ID information.
272 *
273 * @param[in] client Recently connected client.
274 */
275 void
ReserveClientIds(struct _Client * client)276 ReserveClientIds(struct _Client *client)
277 {
278 #ifdef CLIENTIDS
279 if (client == NullClient)
280 return;
281
282 assert(!client->clientIds);
283 client->clientIds = calloc(1, sizeof(ClientIdRec));
284 if (!client->clientIds)
285 return;
286
287 client->clientIds->pid = DetermineClientPid(client);
288 if (client->clientIds->pid != -1)
289 DetermineClientCmd(client->clientIds->pid, &client->clientIds->cmdname,
290 &client->clientIds->cmdargs);
291
292 DebugF("client(%lx): Reserved pid(%d).\n",
293 (unsigned long) client->clientAsMask, client->clientIds->pid);
294 DebugF("client(%lx): Reserved cmdname(%s) and cmdargs(%s).\n",
295 (unsigned long) client->clientAsMask,
296 client->clientIds->cmdname ? client->clientIds->cmdname : "NULL",
297 client->clientIds->cmdargs ? client->clientIds->cmdargs : "NULL");
298 #endif /* CLIENTIDS */
299 }
300
301 /**
302 * Called when an existing client disconnects. Frees client ID
303 * information.
304 *
305 * @param[in] client Recently disconnected client.
306 */
307 void
ReleaseClientIds(struct _Client * client)308 ReleaseClientIds(struct _Client *client)
309 {
310 #ifdef CLIENTIDS
311 if (client == NullClient)
312 return;
313
314 if (!client->clientIds)
315 return;
316
317 DebugF("client(%lx): Released pid(%d).\n",
318 (unsigned long) client->clientAsMask, client->clientIds->pid);
319 DebugF("client(%lx): Released cmdline(%s) and cmdargs(%s).\n",
320 (unsigned long) client->clientAsMask,
321 client->clientIds->cmdname ? client->clientIds->cmdname : "NULL",
322 client->clientIds->cmdargs ? client->clientIds->cmdargs : "NULL");
323
324 free((void *) client->clientIds->cmdname); /* const char * */
325 free((void *) client->clientIds->cmdargs); /* const char * */
326 free(client->clientIds);
327 client->clientIds = NULL;
328 #endif /* CLIENTIDS */
329 }
330
331 /**
332 * Get cached PID of a client.
333 *
334 * param[in] client Client whose PID has been already cached.
335 *
336 * @return Cached client PID. Error (-1) if called:
337 * - before ClientStateInitial client state notification
338 * - after ClientStateGone client state notification
339 * - for remote clients
340 *
341 * @see DetermineClientPid
342 */
343 pid_t
GetClientPid(struct _Client * client)344 GetClientPid(struct _Client *client)
345 {
346 if (client == NullClient)
347 return -1;
348
349 if (!client->clientIds)
350 return -1;
351
352 return client->clientIds->pid;
353 }
354
355 /**
356 * Get cached command name string of a client.
357 *
358 * param[in] client Client whose command line string has been already
359 * cached.
360 *
361 * @return Cached client command name. Error (NULL) if called:
362 * - before ClientStateInitial client state notification
363 * - after ClientStateGone client state notification
364 * - for remote clients
365 * - on OS that doesn't support mapping of PID to command line
366 *
367 * @see DetermineClientCmd
368 */
369 const char *
GetClientCmdName(struct _Client * client)370 GetClientCmdName(struct _Client *client)
371 {
372 if (client == NullClient)
373 return NULL;
374
375 if (!client->clientIds)
376 return NULL;
377
378 return client->clientIds->cmdname;
379 }
380
381 /**
382 * Get cached command arguments string of a client.
383 *
384 * param[in] client Client whose command line string has been already
385 * cached.
386 *
387 * @return Cached client command arguments. Error (NULL) if called:
388 * - before ClientStateInitial client state notification
389 * - after ClientStateGone client state notification
390 * - for remote clients
391 * - on OS that doesn't support mapping of PID to command line
392 *
393 * @see DetermineClientCmd
394 */
395 const char *
GetClientCmdArgs(struct _Client * client)396 GetClientCmdArgs(struct _Client *client)
397 {
398 if (client == NullClient)
399 return NULL;
400
401 if (!client->clientIds)
402 return NULL;
403
404 return client->clientIds->cmdargs;
405 }
406