client.c revision a1e1cf94
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#ifdef __APPLE__
77#include <dispatch/dispatch.h>
78#include <errno.h>
79#include <sys/sysctl.h>
80#endif
81
82/**
83 * Try to determine a PID for a client from its connection
84 * information. This should be called only once when new client has
85 * connected, use GetClientPid to determine the PID at other times.
86 *
87 * @param[in] client Connection linked to some process.
88 *
89 * @return PID of the client. Error (-1) if PID can't be determined
90 *         for the client.
91 *
92 * @see GetClientPid
93 */
94pid_t
95DetermineClientPid(struct _Client * client)
96{
97    LocalClientCredRec *lcc = NULL;
98    pid_t pid = -1;
99
100    if (client == NullClient)
101        return pid;
102
103    if (client == serverClient)
104        return getpid();
105
106    if (GetLocalClientCreds(client, &lcc) != -1) {
107        if (lcc->fieldsSet & LCC_PID_SET)
108            pid = lcc->pid;
109        FreeLocalClientCreds(lcc);
110    }
111
112    return pid;
113}
114
115/**
116 * Try to determine a command line string for a client based on its
117 * PID. Note that mapping PID to a command hasn't been implemented for
118 * some operating systems. This should be called only once when a new
119 * client has connected, use GetClientCmdName/Args to determine the
120 * string at other times.
121 *
122 * @param[in]  pid     Process ID of a client.
123
124 * @param[out] cmdname Client process name without arguments. You must
125 *                     release this by calling free. On error NULL is
126 *                     returned. Pass NULL if you aren't interested in
127 *                     this value.
128 * @param[out] cmdargs Arguments to client process. Useful for
129 *                     identifying a client that is executed from a
130 *                     launcher program. You must release this by
131 *                     calling free. On error NULL is returned. Pass
132 *                     NULL if you aren't interested in this value.
133 *
134 * @see GetClientCmdName/Args
135 */
136void
137DetermineClientCmd(pid_t pid, const char **cmdname, const char **cmdargs)
138{
139#if !defined(__APPLE__)
140    char path[PATH_MAX + 1];
141    int totsize = 0;
142    int fd = 0;
143#endif
144
145    if (cmdname)
146        *cmdname = NULL;
147    if (cmdargs)
148        *cmdargs = NULL;
149
150    if (pid == -1)
151        return;
152
153#if defined (__APPLE__)
154    {
155        static dispatch_once_t once;
156        static int argmax;
157        dispatch_once(&once, ^{
158            int mib[2];
159            size_t len;
160
161            mib[0] = CTL_KERN;
162            mib[1] = KERN_ARGMAX;
163
164            len = sizeof(argmax);
165            if (sysctl(mib, 2, &argmax, &len, NULL, 0) == -1) {
166                ErrorF("Unable to dynamically determine kern.argmax, using ARG_MAX (%d)\n", ARG_MAX);
167                argmax = ARG_MAX;
168            }
169        });
170
171        int mib[3];
172        size_t len = argmax;
173        int32_t argc = -1;
174
175        char * const procargs = malloc(len);
176        if (!procargs) {
177            ErrorF("Failed to allocate memory (%lu bytes) for KERN_PROCARGS2 result for pid %d: %s\n", len, pid, strerror(errno));
178            return;
179        }
180
181        mib[0] = CTL_KERN;
182        mib[1] = KERN_PROCARGS2;
183        mib[2] = pid;
184
185        if (sysctl(mib, 3, procargs, &len, NULL, 0) == -1) {
186            ErrorF("Failed to determine KERN_PROCARGS2 for pid %d: %s\n", pid, strerror(errno));
187            free(procargs);
188            return;
189        }
190
191        if (len < sizeof(argc) || len > argmax) {
192            ErrorF("Erroneous length returned when querying KERN_PROCARGS2 for pid %d: %zu\n", pid, len);
193            free(procargs);
194            return;
195        }
196
197        /* Ensure we have a failsafe NUL termination just in case the last entry
198         * was not actually NUL terminated.
199         */
200        procargs[len-1] = '\0';
201
202        /* Setup our iterator */
203        char *is = procargs;
204
205        /* The first element in the buffer is argc as a 32bit int. When using
206         * the older KERN_PROCARGS, this is omitted, and one needs to guess
207         * (usually by checking for an `=` character) when we start seeing
208         * envvars instead of arguments.
209         */
210        argc = *(int32_t *)is;
211        is += sizeof(argc);
212
213        /* The very next string is the executable path.  Skip over it since
214         * this function wants to return argv[0] and argv[1...n].
215         */
216        is += strlen(is) + 1;
217
218        /* Skip over extra NUL characters to get to the start of argv[0] */
219        for (; (is < &procargs[len]) && !(*is); is++);
220
221        if (! (is < &procargs[len])) {
222            ErrorF("Arguments were not returned when querying KERN_PROCARGS2 for pid %d: %zu\n", pid, len);
223            free(procargs);
224            return;
225        }
226
227        if (cmdname) {
228            *cmdname = strdup(is);
229        }
230
231        /* Jump over argv[0] and point to argv[1] */
232        is += strlen(is) + 1;
233
234        if (cmdargs && is < &procargs[len]) {
235            char *args = is;
236
237            /* Remove the NUL terminators except the last one */
238            for (int i = 1; i < argc - 1; i++) {
239                /* Advance to the NUL terminator */
240                is += strlen(is);
241
242                /* Change the NUL to a space, ensuring we don't accidentally remove the terminal NUL */
243                if (is < &procargs[len-1]) {
244                    *is = ' ';
245                }
246            }
247
248            *cmdargs = strdup(args);
249        }
250
251        free(procargs);
252    }
253#elif defined(__OpenBSD__)
254    /* on OpenBSD use kvm_getargv() */
255    {
256        kvm_t *kd;
257        char errbuf[_POSIX2_LINE_MAX];
258        char **argv;
259        struct kinfo_proc *kp;
260        size_t len = 0;
261        int i, n;
262
263        kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
264        if (kd == NULL)
265            return;
266        kp = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(struct kinfo_proc),
267                          &n);
268        if (n != 1)
269            return;
270        argv = kvm_getargv(kd, kp, 0);
271        *cmdname = strdup(argv[0]);
272        i = 1;
273        while (argv[i] != NULL) {
274            len += strlen(argv[i]) + 1;
275            i++;
276        }
277        *cmdargs = calloc(1, len);
278        i = 1;
279        while (argv[i] != NULL) {
280            strlcat(*cmdargs, argv[i], len);
281            strlcat(*cmdargs, " ", len);
282            i++;
283        }
284        kvm_close(kd);
285    }
286#else                           /* Linux using /proc/pid/cmdline */
287
288    /* Check if /proc/pid/cmdline exists. It's not supported on all
289     * operating systems. */
290    if (snprintf(path, sizeof(path), "/proc/%d/cmdline", pid) < 0)
291        return;
292    fd = open(path, O_RDONLY);
293    if (fd < 0)
294#ifdef __sun
295        goto fallback;
296#else
297        return;
298#endif
299
300    /* Read the contents of /proc/pid/cmdline. It should contain the
301     * process name and arguments. */
302    totsize = read(fd, path, sizeof(path));
303    close(fd);
304    if (totsize <= 0)
305        return;
306    path[totsize - 1] = '\0';
307
308    /* Construct the process name without arguments. */
309    if (cmdname) {
310        *cmdname = strdup(path);
311    }
312
313    /* Construct the arguments for client process. */
314    if (cmdargs) {
315        int cmdsize = strlen(path) + 1;
316        int argsize = totsize - cmdsize;
317        char *args = NULL;
318
319        if (argsize > 0)
320            args = malloc(argsize);
321        if (args) {
322            int i = 0;
323
324            for (i = 0; i < (argsize - 1); ++i) {
325                const char c = path[cmdsize + i];
326
327                args[i] = (c == '\0') ? ' ' : c;
328            }
329            args[argsize - 1] = '\0';
330            *cmdargs = args;
331        }
332    }
333    return;
334#endif
335
336#ifdef __sun                    /* Solaris */
337  fallback:
338    /* Solaris prior to 11.3.5 does not support /proc/pid/cmdline, but
339     * makes information similar to what ps shows available in a binary
340     * structure in the /proc/pid/psinfo file. */
341    if (snprintf(path, sizeof(path), "/proc/%d/psinfo", pid) < 0)
342        return;
343    fd = open(path, O_RDONLY);
344    if (fd < 0) {
345        ErrorF("Failed to open %s: %s\n", path, strerror(errno));
346        return;
347    }
348    else {
349        psinfo_t psinfo = { 0 };
350        char *sp;
351
352        totsize = read(fd, &psinfo, sizeof(psinfo_t));
353        close(fd);
354        if (totsize <= 0)
355            return;
356
357        /* pr_psargs is the first PRARGSZ (80) characters of the command
358         * line string - assume up to the first space is the command name,
359         * since it's not delimited.   While there is also pr_fname, that's
360         * more limited, giving only the first 16 chars of the basename of
361         * the file that was exec'ed, thus cutting off many long gnome
362         * command names, or returning "isapython2.6" for all python scripts.
363         */
364        psinfo.pr_psargs[PRARGSZ - 1] = '\0';
365        sp = strchr(psinfo.pr_psargs, ' ');
366        if (sp)
367            *sp++ = '\0';
368
369        if (cmdname)
370            *cmdname = strdup(psinfo.pr_psargs);
371
372        if (cmdargs && sp)
373            *cmdargs = strdup(sp);
374    }
375#endif
376}
377
378/**
379 * Called when a new client connects. Allocates client ID information.
380 *
381 * @param[in] client Recently connected client.
382 */
383void
384ReserveClientIds(struct _Client *client)
385{
386#ifdef CLIENTIDS
387    if (client == NullClient)
388        return;
389
390    assert(!client->clientIds);
391    client->clientIds = calloc(1, sizeof(ClientIdRec));
392    if (!client->clientIds)
393        return;
394
395    client->clientIds->pid = DetermineClientPid(client);
396    if (client->clientIds->pid != -1)
397        DetermineClientCmd(client->clientIds->pid, &client->clientIds->cmdname,
398                           &client->clientIds->cmdargs);
399
400    DebugF("client(%lx): Reserved pid(%d).\n",
401           (unsigned long) client->clientAsMask, client->clientIds->pid);
402    DebugF("client(%lx): Reserved cmdname(%s) and cmdargs(%s).\n",
403           (unsigned long) client->clientAsMask,
404           client->clientIds->cmdname ? client->clientIds->cmdname : "NULL",
405           client->clientIds->cmdargs ? client->clientIds->cmdargs : "NULL");
406#endif                          /* CLIENTIDS */
407}
408
409/**
410 * Called when an existing client disconnects. Frees client ID
411 * information.
412 *
413 * @param[in] client Recently disconnected client.
414 */
415void
416ReleaseClientIds(struct _Client *client)
417{
418#ifdef CLIENTIDS
419    if (client == NullClient)
420        return;
421
422    if (!client->clientIds)
423        return;
424
425    DebugF("client(%lx): Released pid(%d).\n",
426           (unsigned long) client->clientAsMask, client->clientIds->pid);
427    DebugF("client(%lx): Released cmdline(%s) and cmdargs(%s).\n",
428           (unsigned long) client->clientAsMask,
429           client->clientIds->cmdname ? client->clientIds->cmdname : "NULL",
430           client->clientIds->cmdargs ? client->clientIds->cmdargs : "NULL");
431
432    free((void *) client->clientIds->cmdname);  /* const char * */
433    free((void *) client->clientIds->cmdargs);  /* const char * */
434    free(client->clientIds);
435    client->clientIds = NULL;
436#endif                          /* CLIENTIDS */
437}
438
439/**
440 * Get cached PID of a client.
441 *
442 * param[in] client Client whose PID has been already cached.
443 *
444 * @return Cached client PID. Error (-1) if called:
445 *         - before ClientStateInitial client state notification
446 *         - after ClientStateGone client state notification
447 *         - for remote clients
448 *
449 * @see DetermineClientPid
450 */
451pid_t
452GetClientPid(struct _Client *client)
453{
454    if (client == NullClient)
455        return -1;
456
457    if (!client->clientIds)
458        return -1;
459
460    return client->clientIds->pid;
461}
462
463/**
464 * Get cached command name string of a client.
465 *
466 * param[in] client Client whose command line string has been already
467 *                  cached.
468 *
469 * @return Cached client command name. Error (NULL) if called:
470 *         - before ClientStateInitial client state notification
471 *         - after ClientStateGone client state notification
472 *         - for remote clients
473 *         - on OS that doesn't support mapping of PID to command line
474 *
475 * @see DetermineClientCmd
476 */
477const char *
478GetClientCmdName(struct _Client *client)
479{
480    if (client == NullClient)
481        return NULL;
482
483    if (!client->clientIds)
484        return NULL;
485
486    return client->clientIds->cmdname;
487}
488
489/**
490 * Get cached command arguments string of a client.
491 *
492 * param[in] client Client whose command line string has been already
493 *                  cached.
494 *
495 * @return Cached client command arguments. Error (NULL) if called:
496 *         - before ClientStateInitial client state notification
497 *         - after ClientStateGone client state notification
498 *         - for remote clients
499 *         - on OS that doesn't support mapping of PID to command line
500 *
501 * @see DetermineClientCmd
502 */
503const char *
504GetClientCmdArgs(struct _Client *client)
505{
506    if (client == NullClient)
507        return NULL;
508
509    if (!client->clientIds)
510        return NULL;
511
512    return client->clientIds->cmdargs;
513}
514