1/* 2 * find_pid_by_name from busybox 3 */ 4 5#include <sys/types.h> 6#include <sys/stat.h> 7#include <fcntl.h> 8#include <dirent.h> 9#include <stdlib.h> 10#include <stdio.h> 11#include <errno.h> 12#include <string.h> 13#include <unistd.h> 14#include <stddef.h> 15#include <ctype.h> 16 17enum { 18 PSSCAN_PID = 1 << 0, 19 PSSCAN_PPID = 1 << 1, 20 PSSCAN_PGID = 1 << 2, 21 PSSCAN_SID = 1 << 3, 22 PSSCAN_UIDGID = 1 << 4, 23 PSSCAN_COMM = 1 << 5, 24 /* PSSCAN_CMD = 1 << 6, - use read_cmdline instead */ 25 PSSCAN_ARGV0 = 1 << 7, 26 /* PSSCAN_EXE = 1 << 8, - not implemented */ 27 PSSCAN_STATE = 1 << 9, 28 PSSCAN_VSZ = 1 << 10, 29 PSSCAN_RSS = 1 << 11, 30 PSSCAN_STIME = 1 << 12, 31 PSSCAN_UTIME = 1 << 13, 32 PSSCAN_TTY = 1 << 14, 33 PSSCAN_SMAPS = (1 << 15) * 0, 34 PSSCAN_ARGVN = (1 << 16) * 1, 35 PSSCAN_START_TIME = 1 << 18, 36 /* These are all retrieved from proc/NN/stat in one go: */ 37 PSSCAN_STAT = PSSCAN_PPID | PSSCAN_PGID | PSSCAN_SID 38 | PSSCAN_COMM | PSSCAN_STATE 39 | PSSCAN_VSZ | PSSCAN_RSS 40 | PSSCAN_STIME | PSSCAN_UTIME | PSSCAN_START_TIME 41 | PSSCAN_TTY, 42}; 43 44/* 45In Linux we have three ways to determine "process name": 461. /proc/PID/stat has "...(name)...", among other things. It's so-called "comm" field. 472. /proc/PID/cmdline's first NUL-terminated string. It's argv[0] from exec syscall. 483. /proc/PID/exe symlink. Points to the running executable file. 49 50kernel threads: 51 comm: thread name 52 cmdline: empty 53 exe: <readlink fails> 54 55executable 56 comm: first 15 chars of base name 57 (if executable is a symlink, then first 15 chars of symlink name are used) 58 cmdline: argv[0] from exec syscall 59 exe: points to executable (resolves symlink, unlike comm) 60 61script (an executable with #!/path/to/interpreter): 62 comm: first 15 chars of script's base name (symlinks are not resolved) 63 cmdline: /path/to/interpreter (symlinks are not resolved) 64 (script name is in argv[1], args are pushed into argv[2] etc) 65 exe: points to interpreter's executable (symlinks are resolved) 66 67If FEATURE_PREFER_APPLETS=y (and more so if FEATURE_SH_STANDALONE=y), 68some commands started from busybox shell, xargs or find are started by 69execXXX("/proc/self/exe", applet_name, params....) 70and therefore comm field contains "exe". 71*/ 72 73#define PROCPS_BUFSIZE 1024 74 75static int read_to_buf(const char *filename, void *buf) 76{ 77 int fd; 78 /* open_read_close() would do two reads, checking for EOF. 79 * When you have 10000 /proc/$NUM/stat to read, it isn't desirable */ 80 int ret = -1; 81 fd = open(filename, O_RDONLY); 82 if (fd >= 0) { 83 ret = read(fd, buf, PROCPS_BUFSIZE-1); 84 close(fd); 85 } 86 ((char *)buf)[ret > 0 ? ret : 0] = '\0'; 87 return ret; 88} 89 90void* xzalloc(size_t size) 91{ 92 void *ptr = malloc(size); 93 memset(ptr, 0, size); 94 return ptr; 95} 96 97typedef struct procps_status_t { 98 DIR *dir; 99 unsigned char shift_pages_to_bytes; 100 unsigned char shift_pages_to_kb; 101/* Fields are set to 0/NULL if failed to determine (or not requested) */ 102 unsigned int argv_len; 103 char *argv0; 104 /* Everything below must contain no ptrs to malloc'ed data: 105 * it is memset(0) for each process in procps_scan() */ 106 unsigned long vsz, rss; /* we round it to kbytes */ 107 unsigned long stime, utime; 108 unsigned long start_time; 109 unsigned pid; 110 unsigned ppid; 111 unsigned pgid; 112 unsigned sid; 113 unsigned uid; 114 unsigned gid; 115 unsigned tty_major,tty_minor; 116 char state[4]; 117 /* basename of executable in exec(2), read from /proc/N/stat 118 * (if executable is symlink or script, it is NOT replaced 119 * by link target or interpreter name) */ 120 char comm[16]; 121 /* user/group? - use passwd/group parsing functions */ 122} procps_status_t; 123 124static procps_status_t* alloc_procps_scan(void) 125{ 126 unsigned n = getpagesize(); 127 procps_status_t* sp = xzalloc(sizeof(procps_status_t)); 128 sp->dir = opendir("/proc"); 129 while (1) { 130 n >>= 1; 131 if (!n) break; 132 sp->shift_pages_to_bytes++; 133 } 134 sp->shift_pages_to_kb = sp->shift_pages_to_bytes - 10; 135 return sp; 136} 137 138void BUG_comm_size(void) 139{ 140} 141 142#define ULLONG_MAX (~0ULL) 143#define UINT_MAX (~0U) 144 145static unsigned long long ret_ERANGE(void) 146{ 147 errno = ERANGE; /* this ain't as small as it looks (on glibc) */ 148 return ULLONG_MAX; 149} 150 151static unsigned long long handle_errors(unsigned long long v, char **endp, char *endptr) 152{ 153 if (endp) *endp = endptr; 154 155 /* errno is already set to ERANGE by strtoXXX if value overflowed */ 156 if (endptr[0]) { 157 /* "1234abcg" or out-of-range? */ 158 if (isalnum(endptr[0]) || errno) 159 return ret_ERANGE(); 160 /* good number, just suspicious terminator */ 161 errno = EINVAL; 162 } 163 return v; 164} 165 166unsigned bb_strtou(const char *arg, char **endp, int base) 167{ 168 unsigned long v; 169 char *endptr; 170 171 if (!isalnum(arg[0])) return ret_ERANGE(); 172 errno = 0; 173 v = strtoul(arg, &endptr, base); 174 if (v > UINT_MAX) return ret_ERANGE(); 175 return handle_errors(v, endp, endptr); 176} 177 178unsigned long long bb_strtoull(const char *arg, char **endp, int base) 179{ 180 unsigned long long v; 181 char *endptr; 182 183 /* strtoul(" -4200000000") returns 94967296, errno 0 (!) */ 184 /* I don't think that this is right. Preventing this... */ 185 if (!isalnum(arg[0])) return ret_ERANGE(); 186 187 /* not 100% correct for lib func, but convenient for the caller */ 188 errno = 0; 189 v = strtoull(arg, &endptr, base); 190 return handle_errors(v, endp, endptr); 191} 192 193void* xrealloc(void *ptr, size_t size) 194{ 195 ptr = realloc(ptr, size); 196 if (ptr == NULL && size != 0) 197 perror("no memory"); 198 return ptr; 199} 200 201void* xrealloc_vector_helper(void *vector, unsigned sizeof_and_shift, int idx) 202{ 203 int mask = 1 << (unsigned char)sizeof_and_shift; 204 205 if (!(idx & (mask - 1))) { 206 sizeof_and_shift >>= 8; /* sizeof(vector[0]) */ 207 vector = xrealloc(vector, sizeof_and_shift * (idx + mask + 1)); 208 memset((char*)vector + (sizeof_and_shift * idx), 0, sizeof_and_shift * (mask + 1)); 209 } 210 return vector; 211} 212 213#define xrealloc_vector(vector, shift, idx) \ 214 xrealloc_vector_helper((vector), (sizeof((vector)[0]) << 8) + (shift), (idx)) 215 216void free_procps_scan(procps_status_t* sp) 217{ 218 closedir(sp->dir); 219 free(sp->argv0); 220 free(sp); 221} 222 223procps_status_t* procps_scan(procps_status_t* sp, int flags) 224{ 225 struct dirent *entry; 226 char buf[PROCPS_BUFSIZE]; 227 char filename[sizeof("/proc//cmdline") + sizeof(int)*3]; 228 char *filename_tail; 229 long tasknice; 230 unsigned pid; 231 int n; 232 struct stat sb; 233 234 if (!sp) 235 sp = alloc_procps_scan(); 236 237 for (;;) { 238 entry = readdir(sp->dir); 239 if (entry == NULL) { 240 free_procps_scan(sp); 241 return NULL; 242 } 243 pid = bb_strtou(entry->d_name, NULL, 10); 244 if (errno) 245 continue; 246 247 /* After this point we have to break, not continue 248 * ("continue" would mean that current /proc/NNN 249 * is not a valid process info) */ 250 251 memset(&sp->vsz, 0, sizeof(*sp) - offsetof(procps_status_t, vsz)); 252 253 sp->pid = pid; 254 if (!(flags & ~PSSCAN_PID)) break; 255 256 filename_tail = filename + sprintf(filename, "/proc/%d", pid); 257 258 if (flags & PSSCAN_UIDGID) { 259 if (stat(filename, &sb)) 260 break; 261 /* Need comment - is this effective or real UID/GID? */ 262 sp->uid = sb.st_uid; 263 sp->gid = sb.st_gid; 264 } 265 266 if (flags & PSSCAN_STAT) { 267 char *cp, *comm1; 268 int tty; 269 unsigned long vsz, rss; 270 271 /* see proc(5) for some details on this */ 272 strcpy(filename_tail, "/stat"); 273 n = read_to_buf(filename, buf); 274 if (n < 0) 275 break; 276 cp = strrchr(buf, ')'); /* split into "PID (cmd" and "<rest>" */ 277 /*if (!cp || cp[1] != ' ') 278 break;*/ 279 cp[0] = '\0'; 280 if (sizeof(sp->comm) < 16) 281 BUG_comm_size(); 282 comm1 = strchr(buf, '('); 283 /*if (comm1)*/ 284 strncpy(sp->comm, comm1 + 1, sizeof(sp->comm)); 285 286 n = sscanf(cp+2, 287 "%c %u " /* state, ppid */ 288 "%u %u %d %*s " /* pgid, sid, tty, tpgid */ 289 "%*s %*s %*s %*s %*s " /* flags, min_flt, cmin_flt, maj_flt, cmaj_flt */ 290 "%lu %lu " /* utime, stime */ 291 "%*s %*s %*s " /* cutime, cstime, priority */ 292 "%ld " /* nice */ 293 "%*s %*s " /* timeout, it_real_value */ 294 "%lu " /* start_time */ 295 "%lu " /* vsize */ 296 "%lu " /* rss */ 297 /* "%lu %lu %lu %lu %lu %lu " rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */ 298 /* "%u %u %u %u " signal, blocked, sigignore, sigcatch */ 299 /* "%lu %lu %lu" wchan, nswap, cnswap */ 300 , 301 sp->state, &sp->ppid, 302 &sp->pgid, &sp->sid, &tty, 303 &sp->utime, &sp->stime, 304 &tasknice, 305 &sp->start_time, 306 &vsz, 307 &rss); 308 if (n != 11) 309 break; 310 /* vsz is in bytes and we want kb */ 311 sp->vsz = vsz >> 10; 312 /* vsz is in bytes but rss is in *PAGES*! Can you believe that? */ 313 sp->rss = rss << sp->shift_pages_to_kb; 314 sp->tty_major = (tty >> 8) & 0xfff; 315 sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00); 316 317 if (sp->vsz == 0 && sp->state[0] != 'Z') 318 sp->state[1] = 'W'; 319 else 320 sp->state[1] = ' '; 321 if (tasknice < 0) 322 sp->state[2] = '<'; 323 else if (tasknice) /* > 0 */ 324 sp->state[2] = 'N'; 325 else 326 sp->state[2] = ' '; 327 328 } 329 330 if (flags & (PSSCAN_ARGV0|PSSCAN_ARGVN)) { 331 free(sp->argv0); 332 sp->argv0 = NULL; 333 strcpy(filename_tail, "/cmdline"); 334 n = read_to_buf(filename, buf); 335 if (n <= 0) 336 break; 337 if (flags & PSSCAN_ARGVN) { 338 sp->argv_len = n; 339 sp->argv0 = malloc(n + 1); 340 memcpy(sp->argv0, buf, n + 1); 341 /* sp->argv0[n] = '\0'; - buf has it */ 342 } else { 343 sp->argv_len = 0; 344 sp->argv0 = strdup(buf); 345 } 346 } 347 break; 348 } 349 return sp; 350} 351 352const char* bb_basename(const char *name) 353{ 354 const char *cp = strrchr(name, '/'); 355 if (cp) 356 return cp + 1; 357 return name; 358} 359 360static int comm_match(procps_status_t *p, const char *procName) 361{ 362 int argv1idx; 363 364 /* comm does not match */ 365 if (strncmp(p->comm, procName, 15) != 0) 366 return 0; 367 368 /* in Linux, if comm is 15 chars, it may be a truncated */ 369 if (p->comm[14] == '\0') /* comm is not truncated - match */ 370 return 1; 371 372 /* comm is truncated, but first 15 chars match. 373 * This can be crazily_long_script_name.sh! 374 * The telltale sign is basename(argv[1]) == procName. */ 375 376 if (!p->argv0) 377 return 0; 378 379 argv1idx = strlen(p->argv0) + 1; 380 if (argv1idx >= p->argv_len) 381 return 0; 382 383 if (strcmp(bb_basename(p->argv0 + argv1idx), procName) != 0) 384 return 0; 385 386 return 1; 387} 388 389/* find_pid_by_name() 390 * 391 * Modified by Vladimir Oleynik for use with libbb/procps.c 392 * This finds the pid of the specified process. 393 * Currently, it's implemented by rummaging through 394 * the proc filesystem. 395 * 396 * Returns a list of all matching PIDs 397 * It is the caller's duty to free the returned pidlist. 398 */ 399pid_t* find_pid_by_name(const char *procName) 400{ 401 pid_t* pidList; 402 int i = 0; 403 procps_status_t* p = NULL; 404 405 pidList = xzalloc(sizeof(*pidList)); 406 while ((p = procps_scan(p, PSSCAN_PID|PSSCAN_COMM|PSSCAN_ARGVN))) { 407 if (comm_match(p, procName) 408 /* or we require argv0 to match (essential for matching reexeced /proc/self/exe)*/ 409 || (p->argv0 && strcmp(bb_basename(p->argv0), procName) == 0) 410 /* TOOD: we can also try /proc/NUM/exe link, do we want that? */ 411 ) { 412 if (p->state[0] != 'Z') 413 { 414 pidList = xrealloc_vector(pidList, 2, i); 415 pidList[i++] = p->pid; 416 } 417 } 418 } 419 420 pidList[i] = 0; 421 return pidList; 422} 423 424int pids_main(char *appname) 425{ 426 pid_t *pidList; 427 pid_t *pl; 428 int count = 0; 429 430 pidList = find_pid_by_name(appname); 431 for (pl = pidList; *pl; pl++) { 432 count++; 433 fprintf(stderr, "%u ", (unsigned)*pl); 434 } 435 if (count) fprintf(stderr, "\n"); 436 free(pidList); 437 438 fprintf(stderr, "pid count: %d\n", count); 439 440 return count; 441} 442 443int pids(char *appname) 444{ 445 pid_t *pidList; 446 pid_t *pl; 447 int count = 0; 448 449 pidList = find_pid_by_name(appname); 450 for (pl = pidList; *pl; pl++) { 451 count++; 452 } 453 free(pidList); 454 455 if (count) 456 return 1; 457 else 458 return 0; 459} 460