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 if (cmdname) { 272 if (argv == NULL || argv[0] == NULL) 273 return; 274 else 275 *cmdname = strdup(argv[0]); 276 } 277 if (cmdargs) { 278 i = 1; 279 while (argv[i] != NULL) { 280 len += strlen(argv[i]) + 1; 281 i++; 282 } 283 *cmdargs = calloc(1, len); 284 i = 1; 285 while (argv[i] != NULL) { 286 strlcat(*(char **)cmdargs, argv[i], len); 287 strlcat(*(char **)cmdargs, " ", len); 288 i++; 289 } 290 } 291 kvm_close(kd); 292 } 293#else /* Linux using /proc/pid/cmdline */ 294 295 /* Check if /proc/pid/cmdline exists. It's not supported on all 296 * operating systems. */ 297 if (snprintf(path, sizeof(path), "/proc/%d/cmdline", pid) < 0) 298 return; 299 fd = open(path, O_RDONLY); 300 if (fd < 0) 301#ifdef __sun 302 goto fallback; 303#else 304 return; 305#endif 306 307 /* Read the contents of /proc/pid/cmdline. It should contain the 308 * process name and arguments. */ 309 totsize = read(fd, path, sizeof(path)); 310 close(fd); 311 if (totsize <= 0) 312 return; 313 path[totsize - 1] = '\0'; 314 315 /* Construct the process name without arguments. */ 316 if (cmdname) { 317 *cmdname = strdup(path); 318 } 319 320 /* Construct the arguments for client process. */ 321 if (cmdargs) { 322 int cmdsize = strlen(path) + 1; 323 int argsize = totsize - cmdsize; 324 char *args = NULL; 325 326 if (argsize > 0) 327 args = malloc(argsize); 328 if (args) { 329 int i = 0; 330 331 for (i = 0; i < (argsize - 1); ++i) { 332 const char c = path[cmdsize + i]; 333 334 args[i] = (c == '\0') ? ' ' : c; 335 } 336 args[argsize - 1] = '\0'; 337 *cmdargs = args; 338 } 339 } 340 return; 341#endif 342 343#ifdef __sun /* Solaris */ 344 fallback: 345 /* Solaris prior to 11.3.5 does not support /proc/pid/cmdline, but 346 * makes information similar to what ps shows available in a binary 347 * structure in the /proc/pid/psinfo file. */ 348 if (snprintf(path, sizeof(path), "/proc/%d/psinfo", pid) < 0) 349 return; 350 fd = open(path, O_RDONLY); 351 if (fd < 0) { 352 ErrorF("Failed to open %s: %s\n", path, strerror(errno)); 353 return; 354 } 355 else { 356 psinfo_t psinfo = { 0 }; 357 char *sp; 358 359 totsize = read(fd, &psinfo, sizeof(psinfo_t)); 360 close(fd); 361 if (totsize <= 0) 362 return; 363 364 /* pr_psargs is the first PRARGSZ (80) characters of the command 365 * line string - assume up to the first space is the command name, 366 * since it's not delimited. While there is also pr_fname, that's 367 * more limited, giving only the first 16 chars of the basename of 368 * the file that was exec'ed, thus cutting off many long gnome 369 * command names, or returning "isapython2.6" for all python scripts. 370 */ 371 psinfo.pr_psargs[PRARGSZ - 1] = '\0'; 372 sp = strchr(psinfo.pr_psargs, ' '); 373 if (sp) 374 *sp++ = '\0'; 375 376 if (cmdname) 377 *cmdname = strdup(psinfo.pr_psargs); 378 379 if (cmdargs && sp) 380 *cmdargs = strdup(sp); 381 } 382#endif 383} 384 385/** 386 * Called when a new client connects. Allocates client ID information. 387 * 388 * @param[in] client Recently connected client. 389 */ 390void 391ReserveClientIds(struct _Client *client) 392{ 393#ifdef CLIENTIDS 394 if (client == NullClient) 395 return; 396 397 assert(!client->clientIds); 398 client->clientIds = calloc(1, sizeof(ClientIdRec)); 399 if (!client->clientIds) 400 return; 401 402 client->clientIds->pid = DetermineClientPid(client); 403 if (client->clientIds->pid != -1) 404 DetermineClientCmd(client->clientIds->pid, &client->clientIds->cmdname, 405 &client->clientIds->cmdargs); 406 407 DebugF("client(%lx): Reserved pid(%d).\n", 408 (unsigned long) client->clientAsMask, client->clientIds->pid); 409 DebugF("client(%lx): Reserved cmdname(%s) and cmdargs(%s).\n", 410 (unsigned long) client->clientAsMask, 411 client->clientIds->cmdname ? client->clientIds->cmdname : "NULL", 412 client->clientIds->cmdargs ? client->clientIds->cmdargs : "NULL"); 413#endif /* CLIENTIDS */ 414} 415 416/** 417 * Called when an existing client disconnects. Frees client ID 418 * information. 419 * 420 * @param[in] client Recently disconnected client. 421 */ 422void 423ReleaseClientIds(struct _Client *client) 424{ 425#ifdef CLIENTIDS 426 if (client == NullClient) 427 return; 428 429 if (!client->clientIds) 430 return; 431 432 DebugF("client(%lx): Released pid(%d).\n", 433 (unsigned long) client->clientAsMask, client->clientIds->pid); 434 DebugF("client(%lx): Released cmdline(%s) and cmdargs(%s).\n", 435 (unsigned long) client->clientAsMask, 436 client->clientIds->cmdname ? client->clientIds->cmdname : "NULL", 437 client->clientIds->cmdargs ? client->clientIds->cmdargs : "NULL"); 438 439 free((void *) client->clientIds->cmdname); /* const char * */ 440 free((void *) client->clientIds->cmdargs); /* const char * */ 441 free(client->clientIds); 442 client->clientIds = NULL; 443#endif /* CLIENTIDS */ 444} 445 446/** 447 * Get cached PID of a client. 448 * 449 * param[in] client Client whose PID has been already cached. 450 * 451 * @return Cached client PID. Error (-1) if called: 452 * - before ClientStateInitial client state notification 453 * - after ClientStateGone client state notification 454 * - for remote clients 455 * 456 * @see DetermineClientPid 457 */ 458pid_t 459GetClientPid(struct _Client *client) 460{ 461 if (client == NullClient) 462 return -1; 463 464 if (!client->clientIds) 465 return -1; 466 467 return client->clientIds->pid; 468} 469 470/** 471 * Get cached command name string of a client. 472 * 473 * param[in] client Client whose command line string has been already 474 * cached. 475 * 476 * @return Cached client command name. Error (NULL) if called: 477 * - before ClientStateInitial client state notification 478 * - after ClientStateGone client state notification 479 * - for remote clients 480 * - on OS that doesn't support mapping of PID to command line 481 * 482 * @see DetermineClientCmd 483 */ 484const char * 485GetClientCmdName(struct _Client *client) 486{ 487 if (client == NullClient) 488 return NULL; 489 490 if (!client->clientIds) 491 return NULL; 492 493 return client->clientIds->cmdname; 494} 495 496/** 497 * Get cached command arguments string of a client. 498 * 499 * param[in] client Client whose command line string has been already 500 * cached. 501 * 502 * @return Cached client command arguments. Error (NULL) if called: 503 * - before ClientStateInitial client state notification 504 * - after ClientStateGone client state notification 505 * - for remote clients 506 * - on OS that doesn't support mapping of PID to command line 507 * 508 * @see DetermineClientCmd 509 */ 510const char * 511GetClientCmdArgs(struct _Client *client) 512{ 513 if (client == NullClient) 514 return NULL; 515 516 if (!client->clientIds) 517 return NULL; 518 519 return client->clientIds->cmdargs; 520} 521