void ProcessManager::WarnExcess() { PROCTAB* proc = openproc(PROC_FILLARG | PROC_FILLSTAT | PROC_FILLMEM); proc_t * proc_info; Core::DebugLog("Looking for processes that exceed the hard or soft limit", 10); while (true) { proc_info = readproc(proc, NULL); if (proc_info == NULL) { break; } if (!Configuration::KillRoot && proc_info->euid == 0) { if (Configuration::Verbosity >= 6) { Core::DebugLog("Ignoring " + Core::int2String(proc_info->tid) + " owned by root", 6); } freeproc(proc_info); continue; } if (proc_info->tid == Configuration::pid && !Configuration::KillSelf) { Core::DebugLog("Ignoring " + Core::int2String(proc_info->tid) + " which is current instance of this daemon", 6); freeproc(proc_info); continue; } if (IgnoredId(proc_info->euid)) { Core::DebugLog("Ignoring " + Core::int2String(proc_info->tid) + " owned by ignored account: " + Core::int2String(proc_info->euid), 2); freeproc(proc_info); continue; } // check if this process is using most memory if ( proc_info->resident * 4 > ((long)Configuration::HardMemoryLimitMB * 1024 )) { Core::Log("WARNING: Exceeded hard limit - process " + Name(proc_info)); } else if ( proc_info->resident * 4 > ((long)Configuration::SoftMemoryLimitMB * 1024 )) { Core::Log("WARNING: Exceeded soft limit - process " + Name(proc_info)); } else { if (Configuration::Verbosity > 12) { Core::DebugLog("Not exceeded any limit " + Name(proc_info)); } } freeproc(proc_info); } closeproc(proc); return; }
/* * Pidof functionality. */ int affix_pidof(char *name, int flags, pid_t pid) { PROC *p; PIDQ *q; int i,oind; pid_t opid[PIDOF_OMITSZ], spid = 0; char *basec = NULL; for (oind = PIDOF_OMITSZ-1; oind > 0; oind--) opid[oind] = 0; if (flags&PIDOF_SCRIPTS) scripts_too++; if (flags&PIDOF_OMIT) { opid[oind] = pid; oind++; } if (flags&PIDOF_POMIT) { opid[oind] = getppid(); oind++; } if (flags&PIDOF_BASENAME) { char *ch; basec = strdup(name); name = basename(basec); if ((ch = strchr(name, ' '))) { *ch = '\0'; } } /* Print out process-ID's one by one. */ readproc(); if ((q = pidof(name)) == NULL) goto exit; while ((p = get_next_from_pid_q(q))) { if (flags & PIDOF_OMIT) { for (i = 0; i < oind; i++) { if (opid[i] == p->pid) break; } /* * On a match, continue with * the for loop above. */ if (i < oind) continue; } if (flags & PIDOF_SINGLE) { if (spid) continue; else spid = p->pid; } } exit: free(basec); freeproc(); return spid; }
int do_wait( int *status ) { int children, i; children = 0; for( i = 0; i < NPROC; i++ ) { if ( proc[i].ppid == running->pid ) { children++; } } while( children ) { for (i = 0; i < NPROC; i++) { if (proc[i].status == ZOMBIE && proc[i].ppid == running->pid) { *status = proc[i].exitCode; proc[i].status = FREE; freeproc(&proc[i]); return i; } } ksleep(running); } return -1; }
/** * @brief deallocate the space allocated by readproc if the passed rbuf was NULL * * @param p The rbuf to free */ void standard_freeproc(proc_t* p) { if (!p) { // in case p is NULL return; } #ifdef PROC_EDITCMDLCVT freeproc(p); return; #endif // ptrs are after strings to avoid copying memory when building them. // so free is called on the address of the address of strvec[0]. if (p->cmdline) { free((void*)*p->cmdline); } if (p->environ) { free((void*)*p->environ); } free(p); }
//! Kill process which is eating most void ProcessManager::KillHighest(bool hard) { PROCTAB* proc = openproc(PROC_FILLARG | PROC_FILLSTAT | PROC_FILLMEM); proc_t* proc_info; // zero out the allocated proc_info memory //memset(&proc_info, 0, sizeof(proc_info)); proc_t highest; long current_highest = 0; int current_score = -100; Core::DebugLog("Looking for a best candidate"); while (true) { proc_info = readproc(proc, NULL); if (proc_info == NULL) { break; } if (!Configuration::KillRoot && (int)proc_info->euid == 0) { if (Configuration::Verbosity >= 6) { Core::DebugLog("Ignoring " + Core::int2String(proc_info->tid) + " owned by root", 6); } freeproc(proc_info); continue; } if (IgnoredId(proc_info->euid)) { Core::DebugLog("Ignoring " + Core::int2String(proc_info->tid) + " owned by ignored account: " + Core::int2String(proc_info->euid), 2); freeproc(proc_info); continue; } if (proc_info->tid == Configuration::pid && !Configuration::KillSelf) { Core::DebugLog("Ignoring " + Core::int2String(proc_info->tid) + " which is current instance of this daemon", 6); freeproc(proc_info); continue; } int score = 0; // if it's a root process, decrease the score by 10 if (proc_info->euser == 0) { score -= 10; } score = score + (int)proc_info->nice; int badness_score = Core::GetOom(proc_info->tid); if (badness_score <= -17) { // ignore process freeproc(proc_info); continue; } if (badness_score != 0) { score = score + badness_score; } // check if this process is using most memory if ( proc_info->resident * 4 > current_highest ) { current_highest = proc_info->resident * 4; score += 10; } // if this process has highest score, we flag it for kill if ( score >= current_score ) { highest = *proc_info; current_score = score; } else { if (Configuration::Verbosity >= 12) { Core::DebugLog("Process " + Name(proc_info) + "is eating less than highest candidate", 12); } } freeproc(proc_info); } if (current_score == -100) { Core::ErrorLog("Unable to find any process to kill. System is running OOM and I can't do anything to fix it."); closeproc(proc); return; } Core::Log("Most preferred process has score " + Core::int2String(current_score) + " : " + Name(&highest) + " killing now"); if (KillExec(&highest) == 0) { KillProc((pid_t)highest.tid, hard); Exec(&highest); }else { Core::Log("Not killed " + Name(&highest) + " because the test command returned different value"); } closeproc(proc); return; }
int daemon_main(int argc, char *argv[]) { int opt; bool fork_flag = false; bool replace_flag = false; bool patch_sepolicy = true; enum { OPT_ALLOW_ROOT_CLIENT = 1000, OPT_NO_PATCH_SEPOLICY = 1001, OPT_SIGSTOP_WHEN_READY = 1002, OPT_LOG_TO_KMSG = 1003, OPT_LOG_TO_STDIO = 1004, OPT_NO_UNSHARE = 1005, }; static struct option long_options[] = { {"daemonize", no_argument, 0, 'd'}, {"replace", no_argument, 0, 'r'}, {"help", no_argument, 0, 'h'}, {"allow-root-client", no_argument, 0, OPT_ALLOW_ROOT_CLIENT}, {"no-patch-sepolicy", no_argument, 0, OPT_NO_PATCH_SEPOLICY}, {"sigstop-when-ready", no_argument, 0, OPT_SIGSTOP_WHEN_READY}, {"log-to-kmsg", no_argument, 0, OPT_LOG_TO_KMSG}, {"log-to-stdio", no_argument, 0, OPT_LOG_TO_STDIO}, {"no-unshare", no_argument, 0, OPT_NO_UNSHARE}, {0, 0, 0, 0} }; int long_index = 0; while ((opt = getopt_long(argc, argv, "drh", long_options, &long_index)) != -1) { switch (opt) { case 'd': fork_flag = true; break; case 'r': replace_flag = true; break; case 'h': daemon_usage(0); return EXIT_SUCCESS; case OPT_ALLOW_ROOT_CLIENT: allow_root_client = true; break; case OPT_NO_PATCH_SEPOLICY: patch_sepolicy = false; break; case OPT_SIGSTOP_WHEN_READY: sigstop_when_ready = true; break; case OPT_LOG_TO_KMSG: log_to_kmsg = true; break; case OPT_LOG_TO_STDIO: log_to_stdio = true; break; case OPT_NO_UNSHARE: no_unshare = true; break; default: daemon_usage(1); return EXIT_FAILURE; } } // There should be no other arguments if (argc - optind != 0) { daemon_usage(1); return EXIT_FAILURE; } if (!no_unshare && unshare(CLONE_NEWNS) < 0) { fprintf(stderr, "unshare() failed: %s\n", strerror(errno)); return EXIT_FAILURE; } if (patch_sepolicy) { patch_loaded_sepolicy(SELinuxPatch::MAIN); } if (!switch_context(MB_EXEC_CONTEXT)) { fprintf(stderr, "Failed to switch context; %s may not run properly", argv[0]); } if (replace_flag) { PROCTAB *proc = openproc(PROC_FILLCOM | PROC_FILLSTAT); if (proc) { pid_t curpid = getpid(); while (proc_t *info = readproc(proc, nullptr)) { // NOTE: Can't check 'strcmp(info->cmd, "mbtool") == 0' (which // is the basename of /proc/<pid>/cmd) because the binary is not // always called "mbtool". For example, when run via SignedExec, // it's just called "binary". // If we can read the cmdline and argc >= 2 if (info->cmdline && info->cmdline[0] && info->cmdline[1]) { const char *name = strrchr(info->cmdline[0], '/'); if (name) { ++name; } else { name = info->cmdline[0]; } if (strcmp(name, "mbtool") == 0 // This is mbtool && strstr(info->cmdline[1], "daemon") // And it's a daemon process && info->tid != curpid) { // And we're not killing ourself // Kill the daemon process LOGV("Killing PID %d", info->tid); kill(info->tid, SIGTERM); } } freeproc(info); } closeproc(proc); } // Give processes a chance to exit usleep(500000); } if (fork_flag) { run_daemon_fork(); } else { return (daemon_init() && run_daemon()) ? EXIT_SUCCESS : EXIT_FAILURE; } }
static void select_procs(void) { static size_t size = 0; PROCTAB* ptp; proc_t* task; size_t match; char* cmd_arg0; char* cmd_arg0_base; char* cmd_arg1; char* cmd_arg1_base; char* program_base; char* root_link; char* exe_link; char* exe_link_base; program_base = basename(program); /* get the input base name */ ptp = openproc(PROC_FILLCOM | PROC_FILLSTAT); while ((task = readproc(ptp, NULL))) { if (epidof_root) { /* get the /proc/<pid>/root symlink value */ root_link = pid_link(task->XXXID, "root"); match = !strcmp(epidof_root, root_link); xfree(root_link); if (!match) /* root check failed */ { freeproc(task); continue; } } if (!is_omitted(task->XXXID) && task->cmdline) { cmd_arg0 = task->cmdline[0]; /* processes starting with '-' are login shells */ if (*cmd_arg0 == '-') cmd_arg0++; cmd_arg0_base = basename(cmd_arg0); /* get the argv0 basename */ exe_link = pid_link(task->XXXID, "exe"); /* get the /proc/<pid>/exe symlink value */ exe_link_base = basename(exe_link); /* get the exe_link basename */ match = 0; #define __test(p, c) (!strcmp(p, c##_base) || !strcmp(p, c) || !strcmp(p##_base, c)) if (__test(program, cmd_arg0) || __test(program, exe_link)) match = 1; else if (opt_scripts_too && task->cmdline[1]) { cmd_arg1 = task->cmdline[1]; cmd_arg1_base = basename(cmd_arg1); /* if script, then task->cmd = argv1, otherwise task->cmd = argv0 */ if (task->cmd && !strncmp(task->cmd, cmd_arg1_base, strlen(task->cmd)) && __test(program, cmd_arg1)) match = 1; } #undef __test xfree(exe_link); if (match && environment_test(task->XXXID, *argv)) { if (proc_count == size) procs = xrealloc(procs, __grow(size) * sizeof(*procs)); procs[proc_count++] = task->XXXID; } } freeproc(task); } closeproc(ptp); }
int main(int argc, char ** argv) { char line[1024]; char buf1[1024]; char buf2[1024]; int pid, newpid; char *sp1, *sp2; FILE *wlog = fopen("/dev/null", "w"); FILE *rlog = fopen("/dev/null", "w"); FILE *logfile; int opt; while ( (opt = getopt(argc, argv, "w:r:")) != -1 ) { switch (opt) { case 'w': fclose(wlog); wlog = fopen(optarg, "w"); break; case 'r': fclose(rlog); rlog = fopen(optarg, "w"); break; default: fprintf(stderr, "Usage: %s [-w wlog-file] " "[-r rlog-file]\n", argv[0]); return 1; } } if (fgets(line, 1024, stdin) && sscanf(line, "%d", &pid) == 1 ) { newproc(pid, getcwd((char *) NULL, 0) ); } else { fprintf(stderr, "fl_stparse: Can't init using first line " "of input!\n"); return 1; } do { if ( sscanf(line, "%d fork() = %d", &pid, &newpid) == 2 || sscanf(line, "%d vfork() = %d", &pid, &newpid) == 2 || sscanf(line, "%d <... fork resumed> ) = %d", &pid, &newpid) == 2 || sscanf(line, "%d <... vfork resumed> ) = %d", &pid, &newpid) == 2) { sp1 = getproc(pid)->cwd; sp2 = malloc( strlen(sp1) + 1); strcpy(sp2, sp1); newproc(newpid, sp2 ); continue; } if ( sscanf(line, "%d _exit(%d", &pid, &newpid) == 2 || sscanf(line, "%d exit_group(%d", &pid, &newpid) == 2 ) { freeproc(pid); continue; } if ( sscanf(line, "%d chdir(\"%[^\"]", &pid, buf1) == 2 ) { if (chdir(getproc(pid)->cwd) < 0 || chdir(buf1) < 0) { fprintf(stderr, "fl_stparse: Can't get proc %d's cwd! %s\n", pid, strerror(errno)); exit(1); } setproc(pid, getcwd((char *) NULL, 0) ); continue; } if ( sscanf(line, "%d open(\"%[^\"]\", %s", &pid, buf1, buf2) == 3 ) { if (strstr(buf2, "O_RDONLY") == NULL) logfile = wlog; else logfile = rlog; if (strstr(buf2, "O_DIRECTORY") != NULL) continue; if (buf1[0] == '/') { fprintf(logfile, "%d: %s\n", pid, buf1); } else { sp1 = getproc(pid)->cwd; if (!strcmp(sp1, "/")) sp1=""; fprintf(logfile, "%d: %s/%s\n", pid, sp1, buf1); } continue; } if ( sscanf(line, "%d mkdir(\"%[^\"]\", ", &pid, buf1) == 2 || sscanf(line, "%d utime(\"%[^\"]\", ", &pid, buf1) == 2 || sscanf(line, "%d link(\"%[^\"]\", \"%[^\"]\"", &pid, buf2, buf1) == 3 || sscanf(line, "%d symlink(\"%[^\"]\", \"%[^\"]\"", &pid, buf2, buf1) == 3 || sscanf(line, "%d rename(\"%[^\"]\", \"%[^\"]\"", &pid, buf2, buf1) == 3 ) { if (buf1[0] == '/') { fprintf(wlog, "%d: %s\n", pid, buf1); } else { sp1 = getproc(pid)->cwd; if (!strcmp(sp1, "/")) sp1=""; fprintf(wlog, "%d: %s/%s\n", pid, sp1, buf1); } continue; } } while (fgets(line, 1024, stdin) != NULL); return 0; }
/* * Wait system call. * Search for a terminated (zombie) child, * finally lay it to rest, and collect its status. * Look also for stopped children, * and pass back status from them. */ int waitid(idtype_t idtype, id_t id, k_siginfo_t *ip, int options) { int found; proc_t *cp, *pp; int proc_gone; int waitflag = !(options & WNOWAIT); /* * Obsolete flag, defined here only for binary compatibility * with old statically linked executables. Delete this when * we no longer care about these old and broken applications. */ #define _WNOCHLD 0400 options &= ~_WNOCHLD; if (options == 0 || (options & ~WOPTMASK)) return (EINVAL); switch (idtype) { case P_PID: case P_PGID: if (id < 0 || id >= maxpid) return (EINVAL); /* FALLTHROUGH */ case P_ALL: break; default: return (EINVAL); } pp = ttoproc(curthread); /* * lock parent mutex so that sibling chain can be searched. */ mutex_enter(&pidlock); /* * if we are only looking for exited processes and child_ns list * is empty no reason to look at all children. */ if (idtype == P_ALL && (options & ~WNOWAIT) == (WNOHANG | WEXITED) && pp->p_child_ns == NULL) { if (pp->p_child) { mutex_exit(&pidlock); bzero(ip, sizeof (k_siginfo_t)); return (0); } mutex_exit(&pidlock); return (ECHILD); } while (pp->p_child != NULL) { proc_gone = 0; for (cp = pp->p_child_ns; cp != NULL; cp = cp->p_sibling_ns) { if (idtype != P_PID && (cp->p_pidflag & CLDWAITPID)) continue; if (idtype == P_PID && id != cp->p_pid) continue; if (idtype == P_PGID && id != cp->p_pgrp) continue; switch (cp->p_wcode) { case CLD_TRAPPED: case CLD_STOPPED: case CLD_CONTINUED: cmn_err(CE_PANIC, "waitid: wrong state %d on the p_newstate" " list", cp->p_wcode); break; case CLD_EXITED: case CLD_DUMPED: case CLD_KILLED: if (!(options & WEXITED)) { /* * Count how many are already gone * for good. */ proc_gone++; break; } if (!waitflag) { winfo(cp, ip, 0); } else { winfo(cp, ip, 1); freeproc(cp); } mutex_exit(&pidlock); if (waitflag) { /* accept SIGCLD */ sigcld_delete(ip); sigcld_repost(); } return (0); } if (idtype == P_PID) break; } /* * Wow! None of the threads on the p_sibling_ns list were * interesting threads. Check all the kids! */ found = 0; for (cp = pp->p_child; cp != NULL; cp = cp->p_sibling) { if (idtype == P_PID && id != cp->p_pid) continue; if (idtype == P_PGID && id != cp->p_pgrp) continue; switch (cp->p_wcode) { case CLD_TRAPPED: if (!(options & WTRAPPED)) break; winfo(cp, ip, waitflag); mutex_exit(&pidlock); if (waitflag) { /* accept SIGCLD */ sigcld_delete(ip); sigcld_repost(); } return (0); case CLD_STOPPED: if (!(options & WSTOPPED)) break; /* Is it still stopped? */ mutex_enter(&cp->p_lock); if (!jobstopped(cp)) { mutex_exit(&cp->p_lock); break; } mutex_exit(&cp->p_lock); winfo(cp, ip, waitflag); mutex_exit(&pidlock); if (waitflag) { /* accept SIGCLD */ sigcld_delete(ip); sigcld_repost(); } return (0); case CLD_CONTINUED: if (!(options & WCONTINUED)) break; winfo(cp, ip, waitflag); mutex_exit(&pidlock); if (waitflag) { /* accept SIGCLD */ sigcld_delete(ip); sigcld_repost(); } return (0); case CLD_EXITED: case CLD_DUMPED: case CLD_KILLED: if (idtype != P_PID && (cp->p_pidflag & CLDWAITPID)) continue; /* * Don't complain if a process was found in * the first loop but we broke out of the loop * because of the arguments passed to us. */ if (proc_gone == 0) { cmn_err(CE_PANIC, "waitid: wrong state on the" " p_child list"); } else { break; } } found++; if (idtype == P_PID) break; } /* * If we found no interesting processes at all, * break out and return ECHILD. */ if (found + proc_gone == 0) break; if (options & WNOHANG) { mutex_exit(&pidlock); bzero(ip, sizeof (k_siginfo_t)); /* * We should set ip->si_signo = SIGCLD, * but there is an SVVS test that expects * ip->si_signo to be zero in this case. */ return (0); } /* * If we found no processes of interest that could * change state while we wait, we don't wait at all. * Get out with ECHILD according to SVID. */ if (found == proc_gone) break; if (!cv_wait_sig_swap(&pp->p_cv, &pidlock)) { mutex_exit(&pidlock); return (EINTR); } } mutex_exit(&pidlock); return (ECHILD); }
/* * Return value: * 1 - exitlwps() failed, call (or continue) lwp_exit() * 0 - restarting init. Return through system call path */ int proc_exit(int why, int what) { kthread_t *t = curthread; klwp_t *lwp = ttolwp(t); proc_t *p = ttoproc(t); zone_t *z = p->p_zone; timeout_id_t tmp_id; int rv; proc_t *q; task_t *tk; vnode_t *exec_vp, *execdir_vp, *cdir, *rdir; sigqueue_t *sqp; lwpdir_t *lwpdir; uint_t lwpdir_sz; tidhash_t *tidhash; uint_t tidhash_sz; ret_tidhash_t *ret_tidhash; refstr_t *cwd; hrtime_t hrutime, hrstime; int evaporate; /* * Stop and discard the process's lwps except for the current one, * unless some other lwp beat us to it. If exitlwps() fails then * return and the calling lwp will call (or continue in) lwp_exit(). */ proc_is_exiting(p); if (exitlwps(0) != 0) return (1); mutex_enter(&p->p_lock); if (p->p_ttime > 0) { /* * Account any remaining ticks charged to this process * on its way out. */ (void) task_cpu_time_incr(p->p_task, p->p_ttime); p->p_ttime = 0; } mutex_exit(&p->p_lock); DTRACE_PROC(lwp__exit); DTRACE_PROC1(exit, int, why); /* * Will perform any brand specific proc exit processing, since this * is always the last lwp, will also perform lwp_exit and free brand * data */ if (PROC_IS_BRANDED(p)) { lwp_detach_brand_hdlrs(lwp); brand_clearbrand(p, B_FALSE); } /* * Don't let init exit unless zone_start_init() failed its exec, or * we are shutting down the zone or the machine. * * Since we are single threaded, we don't need to lock the * following accesses to zone_proc_initpid. */ if (p->p_pid == z->zone_proc_initpid) { if (z->zone_boot_err == 0 && zone_status_get(z) < ZONE_IS_SHUTTING_DOWN && zone_status_get(global_zone) < ZONE_IS_SHUTTING_DOWN && z->zone_restart_init == B_TRUE && restart_init(what, why) == 0) return (0); /* * Since we didn't or couldn't restart init, we clear * the zone's init state and proceed with exit * processing. */ z->zone_proc_initpid = -1; } lwp_pcb_exit(); /* * Allocate a sigqueue now, before we grab locks. * It will be given to sigcld(), below. * Special case: If we will be making the process disappear * without a trace because it is either: * * an exiting SSYS process, or * * a posix_spawn() vfork child who requests it, * we don't bother to allocate a useless sigqueue. */ evaporate = (p->p_flag & SSYS) || ((p->p_flag & SVFORK) && why == CLD_EXITED && what == _EVAPORATE); if (!evaporate) sqp = kmem_zalloc(sizeof (sigqueue_t), KM_SLEEP); /* * revoke any doors created by the process. */ if (p->p_door_list) door_exit(); /* * Release schedctl data structures. */ if (p->p_pagep) schedctl_proc_cleanup(); /* * make sure all pending kaio has completed. */ if (p->p_aio) aio_cleanup_exit(); /* * discard the lwpchan cache. */ if (p->p_lcp != NULL) lwpchan_destroy_cache(0); /* * Clean up any DTrace helper actions or probes for the process. */ if (p->p_dtrace_helpers != NULL) { ASSERT(dtrace_helpers_cleanup != NULL); (*dtrace_helpers_cleanup)(); } /* untimeout the realtime timers */ if (p->p_itimer != NULL) timer_exit(); if ((tmp_id = p->p_alarmid) != 0) { p->p_alarmid = 0; (void) untimeout(tmp_id); } /* * Remove any fpollinfo_t's for this (last) thread from our file * descriptors so closeall() can ASSERT() that they're all gone. */ pollcleanup(); if (p->p_rprof_cyclic != CYCLIC_NONE) { mutex_enter(&cpu_lock); cyclic_remove(p->p_rprof_cyclic); mutex_exit(&cpu_lock); } mutex_enter(&p->p_lock); /* * Clean up any DTrace probes associated with this process. */ if (p->p_dtrace_probes) { ASSERT(dtrace_fasttrap_exit_ptr != NULL); dtrace_fasttrap_exit_ptr(p); } while ((tmp_id = p->p_itimerid) != 0) { p->p_itimerid = 0; mutex_exit(&p->p_lock); (void) untimeout(tmp_id); mutex_enter(&p->p_lock); } lwp_cleanup(); /* * We are about to exit; prevent our resource associations from * being changed. */ pool_barrier_enter(); /* * Block the process against /proc now that we have really * acquired p->p_lock (to manipulate p_tlist at least). */ prbarrier(p); sigfillset(&p->p_ignore); sigemptyset(&p->p_siginfo); sigemptyset(&p->p_sig); sigemptyset(&p->p_extsig); sigemptyset(&t->t_sig); sigemptyset(&t->t_extsig); sigemptyset(&p->p_sigmask); sigdelq(p, t, 0); lwp->lwp_cursig = 0; lwp->lwp_extsig = 0; p->p_flag &= ~(SKILLED | SEXTKILLED); if (lwp->lwp_curinfo) { siginfofree(lwp->lwp_curinfo); lwp->lwp_curinfo = NULL; } t->t_proc_flag |= TP_LWPEXIT; ASSERT(p->p_lwpcnt == 1 && p->p_zombcnt == 0); prlwpexit(t); /* notify /proc */ lwp_hash_out(p, t->t_tid); prexit(p); p->p_lwpcnt = 0; p->p_tlist = NULL; sigqfree(p); term_mstate(t); p->p_mterm = gethrtime(); exec_vp = p->p_exec; execdir_vp = p->p_execdir; p->p_exec = NULLVP; p->p_execdir = NULLVP; mutex_exit(&p->p_lock); pr_free_watched_pages(p); closeall(P_FINFO(p)); /* Free the controlling tty. (freectty() always assumes curproc.) */ ASSERT(p == curproc); (void) freectty(B_TRUE); #if defined(__sparc) if (p->p_utraps != NULL) utrap_free(p); #endif if (p->p_semacct) /* IPC semaphore exit */ semexit(p); rv = wstat(why, what); acct(rv & 0xff); exacct_commit_proc(p, rv); /* * Release any resources associated with C2 auditing */ if (AU_AUDITING()) { /* * audit exit system call */ audit_exit(why, what); } /* * Free address space. */ relvm(); if (exec_vp) { /* * Close this executable which has been opened when the process * was created by getproc(). */ (void) VOP_CLOSE(exec_vp, FREAD, 1, (offset_t)0, CRED(), NULL); VN_RELE(exec_vp); } if (execdir_vp) VN_RELE(execdir_vp); /* * Release held contracts. */ contract_exit(p); /* * Depart our encapsulating process contract. */ if ((p->p_flag & SSYS) == 0) { ASSERT(p->p_ct_process); contract_process_exit(p->p_ct_process, p, rv); } /* * Remove pool association, and block if requested by pool_do_bind. */ mutex_enter(&p->p_lock); ASSERT(p->p_pool->pool_ref > 0); atomic_add_32(&p->p_pool->pool_ref, -1); p->p_pool = pool_default; /* * Now that our address space has been freed and all other threads * in this process have exited, set the PEXITED pool flag. This * tells the pools subsystems to ignore this process if it was * requested to rebind this process to a new pool. */ p->p_poolflag |= PEXITED; pool_barrier_exit(); mutex_exit(&p->p_lock); mutex_enter(&pidlock); /* * Delete this process from the newstate list of its parent. We * will put it in the right place in the sigcld in the end. */ delete_ns(p->p_parent, p); /* * Reassign the orphans to the next of kin. * Don't rearrange init's orphanage. */ if ((q = p->p_orphan) != NULL && p != proc_init) { proc_t *nokp = p->p_nextofkin; for (;;) { q->p_nextofkin = nokp; if (q->p_nextorph == NULL) break; q = q->p_nextorph; } q->p_nextorph = nokp->p_orphan; nokp->p_orphan = p->p_orphan; p->p_orphan = NULL; } /* * Reassign the children to init. * Don't try to assign init's children to init. */ if ((q = p->p_child) != NULL && p != proc_init) { struct proc *np; struct proc *initp = proc_init; boolean_t setzonetop = B_FALSE; if (!INGLOBALZONE(curproc)) setzonetop = B_TRUE; pgdetach(p); do { np = q->p_sibling; /* * Delete it from its current parent new state * list and add it to init new state list */ delete_ns(q->p_parent, q); q->p_ppid = 1; q->p_pidflag &= ~(CLDNOSIGCHLD | CLDWAITPID); if (setzonetop) { mutex_enter(&q->p_lock); q->p_flag |= SZONETOP; mutex_exit(&q->p_lock); } q->p_parent = initp; /* * Since q will be the first child, * it will not have a previous sibling. */ q->p_psibling = NULL; if (initp->p_child) { initp->p_child->p_psibling = q; } q->p_sibling = initp->p_child; initp->p_child = q; if (q->p_proc_flag & P_PR_PTRACE) { mutex_enter(&q->p_lock); sigtoproc(q, NULL, SIGKILL); mutex_exit(&q->p_lock); } /* * sigcld() will add the child to parents * newstate list. */ if (q->p_stat == SZOMB) sigcld(q, NULL); } while ((q = np) != NULL); p->p_child = NULL; ASSERT(p->p_child_ns == NULL); } TRACE_1(TR_FAC_PROC, TR_PROC_EXIT, "proc_exit: %p", p); mutex_enter(&p->p_lock); CL_EXIT(curthread); /* tell the scheduler that curthread is exiting */ /* * Have our task accummulate our resource usage data before they * become contaminated by p_cacct etc., and before we renounce * membership of the task. * * We do this regardless of whether or not task accounting is active. * This is to avoid having nonsense data reported for this task if * task accounting is subsequently enabled. The overhead is minimal; * by this point, this process has accounted for the usage of all its * LWPs. We nonetheless do the work here, and under the protection of * pidlock, so that the movement of the process's usage to the task * happens at the same time as the removal of the process from the * task, from the point of view of exacct_snapshot_task_usage(). */ exacct_update_task_mstate(p); hrutime = mstate_aggr_state(p, LMS_USER); hrstime = mstate_aggr_state(p, LMS_SYSTEM); p->p_utime = (clock_t)NSEC_TO_TICK(hrutime) + p->p_cutime; p->p_stime = (clock_t)NSEC_TO_TICK(hrstime) + p->p_cstime; p->p_acct[LMS_USER] += p->p_cacct[LMS_USER]; p->p_acct[LMS_SYSTEM] += p->p_cacct[LMS_SYSTEM]; p->p_acct[LMS_TRAP] += p->p_cacct[LMS_TRAP]; p->p_acct[LMS_TFAULT] += p->p_cacct[LMS_TFAULT]; p->p_acct[LMS_DFAULT] += p->p_cacct[LMS_DFAULT]; p->p_acct[LMS_KFAULT] += p->p_cacct[LMS_KFAULT]; p->p_acct[LMS_USER_LOCK] += p->p_cacct[LMS_USER_LOCK]; p->p_acct[LMS_SLEEP] += p->p_cacct[LMS_SLEEP]; p->p_acct[LMS_WAIT_CPU] += p->p_cacct[LMS_WAIT_CPU]; p->p_acct[LMS_STOPPED] += p->p_cacct[LMS_STOPPED]; p->p_ru.minflt += p->p_cru.minflt; p->p_ru.majflt += p->p_cru.majflt; p->p_ru.nswap += p->p_cru.nswap; p->p_ru.inblock += p->p_cru.inblock; p->p_ru.oublock += p->p_cru.oublock; p->p_ru.msgsnd += p->p_cru.msgsnd; p->p_ru.msgrcv += p->p_cru.msgrcv; p->p_ru.nsignals += p->p_cru.nsignals; p->p_ru.nvcsw += p->p_cru.nvcsw; p->p_ru.nivcsw += p->p_cru.nivcsw; p->p_ru.sysc += p->p_cru.sysc; p->p_ru.ioch += p->p_cru.ioch; p->p_stat = SZOMB; p->p_proc_flag &= ~P_PR_PTRACE; p->p_wdata = what; p->p_wcode = (char)why; cdir = PTOU(p)->u_cdir; rdir = PTOU(p)->u_rdir; cwd = PTOU(p)->u_cwd; ASSERT(cdir != NULL || p->p_parent == &p0); /* * Release resource controls, as they are no longer enforceable. */ rctl_set_free(p->p_rctls); /* * Decrement tk_nlwps counter for our task.max-lwps resource control. * An extended accounting record, if that facility is active, is * scheduled to be written. We cannot give up task and project * membership at this point because that would allow zombies to escape * from the max-processes resource controls. Zombies stay in their * current task and project until the process table slot is released * in freeproc(). */ tk = p->p_task; mutex_enter(&p->p_zone->zone_nlwps_lock); tk->tk_nlwps--; tk->tk_proj->kpj_nlwps--; p->p_zone->zone_nlwps--; mutex_exit(&p->p_zone->zone_nlwps_lock); /* * Clear the lwp directory and the lwpid hash table * now that /proc can't bother us any more. * We free the memory below, after dropping p->p_lock. */ lwpdir = p->p_lwpdir; lwpdir_sz = p->p_lwpdir_sz; tidhash = p->p_tidhash; tidhash_sz = p->p_tidhash_sz; ret_tidhash = p->p_ret_tidhash; p->p_lwpdir = NULL; p->p_lwpfree = NULL; p->p_lwpdir_sz = 0; p->p_tidhash = NULL; p->p_tidhash_sz = 0; p->p_ret_tidhash = NULL; /* * If the process has context ops installed, call the exit routine * on behalf of this last remaining thread. Normally exitpctx() is * called during thread_exit() or lwp_exit(), but because this is the * last thread in the process, we must call it here. By the time * thread_exit() is called (below), the association with the relevant * process has been lost. * * We also free the context here. */ if (p->p_pctx) { kpreempt_disable(); exitpctx(p); kpreempt_enable(); freepctx(p, 0); } /* * curthread's proc pointer is changed to point to the 'sched' * process for the corresponding zone, except in the case when * the exiting process is in fact a zsched instance, in which * case the proc pointer is set to p0. We do so, so that the * process still points at the right zone when we call the VN_RELE() * below. * * This is because curthread's original proc pointer can be freed as * soon as the child sends a SIGCLD to its parent. We use zsched so * that for user processes, even in the final moments of death, the * process is still associated with its zone. */ if (p != t->t_procp->p_zone->zone_zsched) t->t_procp = t->t_procp->p_zone->zone_zsched; else t->t_procp = &p0; mutex_exit(&p->p_lock); if (!evaporate) { p->p_pidflag &= ~CLDPEND; sigcld(p, sqp); } else { /* * Do what sigcld() would do if the disposition * of the SIGCHLD signal were set to be ignored. */ cv_broadcast(&p->p_srwchan_cv); freeproc(p); } mutex_exit(&pidlock); /* * We don't release u_cdir and u_rdir until SZOMB is set. * This protects us against dofusers(). */ if (cdir) VN_RELE(cdir); if (rdir) VN_RELE(rdir); if (cwd) refstr_rele(cwd); /* * task_rele() may ultimately cause the zone to go away (or * may cause the last user process in a zone to go away, which * signals zsched to go away). So prior to this call, we must * no longer point at zsched. */ t->t_procp = &p0; kmem_free(lwpdir, lwpdir_sz * sizeof (lwpdir_t)); kmem_free(tidhash, tidhash_sz * sizeof (tidhash_t)); while (ret_tidhash != NULL) { ret_tidhash_t *next = ret_tidhash->rth_next; kmem_free(ret_tidhash->rth_tidhash, ret_tidhash->rth_tidhash_sz * sizeof (tidhash_t)); kmem_free(ret_tidhash, sizeof (*ret_tidhash)); ret_tidhash = next; } thread_exit(); /* NOTREACHED */ }
/* * Read the proc filesystem. */ static int readproc(void) { DIR *dir; struct dirent *d; char path[256]; char buf[256]; char *s, *q; FILE *fp; int pid, f; PROC *p; struct stat st; int c; /* Open the /proc directory. */ if ((dir = opendir("/proc")) == NULL) { BTERROR("cannot opendir(/proc)"); return -1; } freeproc(); /* Walk through the directory. */ while ((d = readdir(dir)) != NULL) { /* See if this is a process */ if ((pid = atoi(d->d_name)) == 0) continue; /* Get a PROC struct . */ p = (PROC *)malloc(sizeof(PROC)); memset(p, 0, sizeof(PROC)); /* Open the status file. */ snprintf(path, sizeof(path), "/proc/%s/stat", d->d_name); /* Read SID & statname from it. */ if ((fp = fopen(path, "r")) != NULL) { buf[0] = 0; fgets(buf, 256, fp); /* See if name starts with '(' */ s = buf; while (*s != ' ') s++; s++; if (*s == '(') { /* Read program name. */ q = strrchr(buf, ')'); if (q == NULL) { p->sid = 0; BTERROR("can't get program name from %s\n", path); free(p); continue; } s++; } else { q = s; while (*q != ' ') q++; } *q++ = 0; while (*q == ' ') q++; p->statname = strdup(s); /* This could be replaced by getsid(pid) */ if (sscanf(q, "%*c %*d %*d %d", &p->sid) != 1) { p->sid = 0; BTERROR("can't read sid from %s\n", path); free(p); continue; } fclose(fp); } else { /* Process disappeared.. */ free(p); continue; } /* Now read argv[0] */ snprintf(path, sizeof(path), "/proc/%s/cmdline", d->d_name); if ((fp = fopen(path, "r")) != NULL) { f = 0; while(f < 127 && (c = fgetc(fp)) != EOF && c) buf[f++] = c; buf[f++] = 0; fclose(fp); /* Store the name into malloced memory. */ p->fullname = strdup(buf); /* Get a pointer to the basename. */ if ((p->basename = strrchr(p->fullname, '/')) != NULL) p->basename++; else p->basename = p->fullname; } else { /* Process disappeared.. */ free(p); continue; } /* Try to stat the executable. */ snprintf(path, sizeof(path), "/proc/%s/exe", d->d_name); if (stat(path, &st) == 0) { p->dev = st.st_dev; p->ino = st.st_ino; } /* Link it into the list. */ p->next = plist; plist = p; p->pid = pid; } closedir(dir); /* Done. */ return 0; }
int daemon_main(int argc, char *argv[]) { int opt; bool fork_flag = false; bool replace_flag = false; static struct option long_options[] = { {"daemonize", no_argument, 0, 'd'}, {"replace", no_argument, 0, 'r'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; int long_index = 0; while ((opt = getopt_long(argc, argv, "drh", long_options, &long_index)) != -1) { switch (opt) { case 'd': fork_flag = true; break; case 'r': replace_flag = true; break; case 'h': daemon_usage(0); return EXIT_SUCCESS; default: daemon_usage(1); return EXIT_FAILURE; } } // There should be no other arguments if (argc - optind != 0) { daemon_usage(1); return EXIT_FAILURE; } // Patch SELinux policy to make init permissive patch_loaded_sepolicy(); // Allow untrusted_app to connect to our daemon patch_sepolicy_daemon(); // Set version property if we're the system mbtool (i.e. launched by init) // Possible to override this with another program by double forking, letting // 2nd child reparent to init, and then calling execve("/mbtool", ...), but // meh ... if (getppid() == 1) { if (!util::set_property("ro.multiboot.version", get_mbtool_version())) { std::printf("Failed to set 'ro.multiboot.version' to '%s'\n", get_mbtool_version()); } } if (replace_flag) { PROCTAB *proc = openproc(PROC_FILLCOM | PROC_FILLSTAT); if (proc) { pid_t curpid = getpid(); while (proc_t *info = readproc(proc, nullptr)) { if (strcmp(info->cmd, "mbtool") == 0 // This is mbtool && info->cmdline // And we can see the command line && info->cmdline[1] // And argc > 1 && strstr(info->cmdline[1], "daemon") // And it's a daemon process && info->tid != curpid) { // And we're not killing ourself // Kill the daemon process std::printf("Killing PID %d\n", info->tid); kill(info->tid, SIGTERM); } freeproc(info); } closeproc(proc); } // Give processes a chance to exit usleep(500000); } // Set up logging if (!util::mkdir_parent(MULTIBOOT_LOG_DAEMON, 0775) && errno != EEXIST) { fprintf(stderr, "Failed to create parent directory of %s: %s\n", MULTIBOOT_LOG_DAEMON, strerror(errno)); return EXIT_FAILURE; } autoclose::file fp(autoclose::fopen(MULTIBOOT_LOG_DAEMON, "w")); if (!fp) { fprintf(stderr, "Failed to open log file %s: %s\n", MULTIBOOT_LOG_DAEMON, strerror(errno)); return EXIT_FAILURE; } fix_multiboot_permissions(); // mbtool logging log::log_set_logger(std::make_shared<log::StdioLogger>(fp.get(), true)); if (fork_flag) { run_daemon_fork(); } else { return run_daemon() ? EXIT_SUCCESS : EXIT_FAILURE; } }