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