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