client.c revision 1b5d61b8
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 */ 88pid_t 89DetermineClientPid(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 */ 130void 131DetermineClientCmd(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 */ 275void 276ReserveClientIds(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 */ 307void 308ReleaseClientIds(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 */ 343pid_t 344GetClientPid(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 */ 369const char * 370GetClientCmdName(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 */ 395const char * 396GetClientCmdArgs(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