Index: sys/kern/kern_cpuset.c =================================================================== --- sys/kern/kern_cpuset.c +++ sys/kern/kern_cpuset.c @@ -1100,18 +1100,67 @@ if (error) return (error); - return cpuset_shadow(tdset, nsetp, &mask, &domain, freelist, + return cpuset_shadow(set, nsetp, &mask, &domain, freelist, domainlist); } +static int +cpuset_setproc_newbase(struct thread *td, struct cpuset *set, + struct cpuset *nroot, struct cpuset **nsetp, + struct setlist *cpusets, struct domainlist *domainlist) +{ + struct domainset ndomain; + cpuset_t nmask; + struct cpuset *pbase; + int error; + + pbase = cpuset_getbase(td->td_cpuset); + + /* Copy process mask, then further apply the new root mask. */ + CPU_COPY(&pbase->cs_mask, &nmask); + CPU_AND(&nmask, &nroot->cs_mask); + + domainset_copy(pbase->cs_domain, &ndomain); + DOMAINSET_AND(&ndomain.ds_mask, &set->cs_domain->ds_mask); + + /* Policy is too restrictive, will not work. */ + if (CPU_EMPTY(&nmask) || DOMAINSET_EMPTY(&ndomain.ds_mask)) + return (EDEADLK); + + /* + * Remove pbase from the freelist in advance, it'll be pushed to + * cpuset_ids on success. We assume here that cpuset_create() will not + * touch pbase on failure, and we just enqueue it back to the freelist + * to remain in a consistent state. + */ + pbase = LIST_FIRST(cpusets); + LIST_REMOVE(pbase, cs_link); + error = cpuset_create(&pbase, set, &nmask); + if (error != 0) { + LIST_INSERT_HEAD(cpusets, pbase, cs_link); + return (error); + } + + /* Duplicates some work from above... oh well. */ + pbase->cs_domain = domainset_shadow(set->cs_domain, &ndomain, + domainlist); + *nsetp = pbase; + return (0); +} + /* - * Handle three cases for updating an entire process. + * Handle four cases for updating an entire process. * - * 1) Set is non-null. This reparents all anonymous sets to the provided - * set and replaces all non-anonymous td_cpusets with the provided set. - * 2) Mask is non-null. This replaces or creates anonymous sets for every + * 1) Set is non-null and the process is not rebasing onto a new root. This + * reparents all anonymous sets to the provided set and replaces all + * non-anonymous td_cpusets with the provided set. + * 2) Set is non-null and the process is rebasing onto a new root. This + * creates a new base set if the process previously had its own base set, + * then reparents all anonymous sets either to that set or the provided set + * if one was not created. Non-anonymous sets are similarly replaced. + * 3) Mask is non-null. This replaces or creates anonymous sets for every * thread with the existing base as a parent. - * 3) domain is non-null. This creates anonymous sets for every thread + * 4) domain is non-null. This creates anonymous sets for every thread * and replaces the domain set. * * This is overly complicated because we can't allocate while holding a @@ -1120,15 +1169,15 @@ */ static int cpuset_setproc(pid_t pid, struct cpuset *set, cpuset_t *mask, - struct domainset *domain) + struct domainset *domain, bool rebase) { struct setlist freelist; struct setlist droplist; struct domainlist domainlist; - struct cpuset *nset; + struct cpuset *base, *nset, *nroot, *tdroot; struct thread *td; struct proc *p; - int threads; + int needed; int nfree; int error; @@ -1144,21 +1193,49 @@ nfree = 1; LIST_INIT(&droplist); nfree = 0; + base = set; + nroot = NULL; + if (set != NULL) + nroot = cpuset_getroot(set); for (;;) { error = cpuset_which(CPU_WHICH_PID, pid, &p, &td, &nset); if (error) goto out; - if (nfree >= p->p_numthreads) + tdroot = cpuset_getroot(td->td_cpuset); + needed = p->p_numthreads; + if (set != NULL && rebase && tdroot != nroot) + needed++; + if (nfree >= needed) break; - threads = p->p_numthreads; PROC_UNLOCK(p); - if (nfree < threads) { - cpuset_freelist_add(&freelist, threads - nfree); - domainset_freelist_add(&domainlist, threads - nfree); - nfree = threads; + if (nfree < needed) { + cpuset_freelist_add(&freelist, needed - nfree); + domainset_freelist_add(&domainlist, needed - nfree); + nfree = needed; } } PROC_LOCK_ASSERT(p, MA_OWNED); + + /* + * If we're changing roots and the root set is what has been specified + * as the parent, then we'll check if the process was previously using + * the root set and, if it wasn't, create a new base with the process's + * mask applied to it. + */ + if (set != NULL && rebase && nroot != tdroot) { + cpusetid_t base_id, root_id; + + root_id = td->td_ucred->cr_prison->pr_cpuset->cs_id; + base_id = cpuset_getbase(td->td_cpuset)->cs_id; + + if (base_id != root_id) { + error = cpuset_setproc_newbase(td, set, nroot, &base, + &freelist, &domainlist); + if (error != 0) + goto unlock_out; + } + } + /* * Now that the appropriate locks are held and we have enough cpusets, * make sure the operation will succeed before applying changes. The @@ -1169,7 +1246,7 @@ thread_lock(td); if (set != NULL) error = cpuset_setproc_test_setthread(td->td_cpuset, - set); + base); else error = cpuset_setproc_test_maskthread(td->td_cpuset, mask, domain); @@ -1185,7 +1262,7 @@ FOREACH_THREAD_IN_PROC(p, td) { thread_lock(td); if (set != NULL) - error = cpuset_setproc_setthread(td->td_cpuset, set, + error = cpuset_setproc_setthread(td->td_cpuset, base, &nset, &freelist, &domainlist); else error = cpuset_setproc_maskthread(td->td_cpuset, mask, @@ -1200,6 +1277,8 @@ unlock_out: PROC_UNLOCK(p); out: + if (base != NULL && base != set) + cpuset_rel(base); while ((nset = LIST_FIRST(&droplist)) != NULL) cpuset_rel_complete(nset); cpuset_freelist_free(&freelist); @@ -1586,7 +1665,7 @@ KASSERT(set != NULL, ("[%s:%d] invalid set", __func__, __LINE__)); cpuset_ref(set); - error = cpuset_setproc(p->p_pid, set, NULL, NULL); + error = cpuset_setproc(p->p_pid, set, NULL, NULL, true); if (error) return (error); cpuset_rel(set); @@ -1636,7 +1715,7 @@ return (error); error = copyout(&set->cs_id, uap->setid, sizeof(set->cs_id)); if (error == 0) - error = cpuset_setproc(-1, set, NULL, NULL); + error = cpuset_setproc(-1, set, NULL, NULL, false); cpuset_rel(set); return (error); } @@ -1670,7 +1749,7 @@ set = cpuset_lookup(setid, td); if (set == NULL) return (ESRCH); - error = cpuset_setproc(id, set, NULL, NULL); + error = cpuset_setproc(id, set, NULL, NULL, false); cpuset_rel(set); return (error); } @@ -1944,7 +2023,7 @@ error = cpuset_setthread(id, mask); break; case CPU_WHICH_PID: - error = cpuset_setproc(id, NULL, mask, NULL); + error = cpuset_setproc(id, NULL, mask, NULL, false); break; case CPU_WHICH_CPUSET: case CPU_WHICH_JAIL: @@ -2228,7 +2307,7 @@ error = _cpuset_setthread(id, NULL, &domain); break; case CPU_WHICH_PID: - error = cpuset_setproc(id, NULL, NULL, &domain); + error = cpuset_setproc(id, NULL, NULL, &domain, false); break; case CPU_WHICH_CPUSET: case CPU_WHICH_JAIL: