mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
Merge patch series "files: make closing files synchronous for close_range(), exec, exit"
Christian Brauner <brauner@kernel.org> says:
For tasks exiting with a large file descriptor table this means getting
rid of _a lot_ of cmpxchg()s and hammering on task->pi_lock. The cost of
this was mentioned in various threads over the years. I found at least
recent comments by Oleg and Neil.
The obvious problematic case are kernel threads. fput() punts their
final __fput() to a workqueue. kthreads never return to userspace to run
task work and some of them may need to finish a umount and so cannot
->release() inline. No path we change is taken by a kthread. They don't
own a descriptor table and kthreadd is created with CLONE_FILES and
every kthread inherits it. So all of them share init_files and init_task
pins that forever. Their exit_files() never drops the last reference.
kernel_execve() refuses PF_KTHREAD outright. Usermodehelpers are spawned
without the kthread flag. close_range() is a syscall. The one indirect
way is a failing copy_process() calling exit_files() on a
child from a kthread parent. A dup_fd() in copy_process() cannot hold
the last reference to any of its files while the source table is alive.
Worker threads such as io_uring workers are user threads without
PF_KTHREAD that share the fdtable. One of them can be the last to put
the fdtable at exit. Today fput() hands their final __fput()s to task
work. The worker runs that itself in exit_task_work() a few lines later.
Now the same thread does the same work in exit_files() instead. vhost
workers are created without a descriptor table at all, so they never
even put an fdtable. Neither kind ever execs or calls close_range().
Now the performance numbers. I measured this on a 64 vCPU KVM guest (AMD
EPYC 9754) on top of vfs-7.4.coredump with and without the series and
the same x86_64_defconfig for both. The benchmark opens N files in a
forked worker, moves them to random positions in the descriptor table so
that the table order has nothing to do with the allocation order, and
times the teardown:
- process exit
- execve() with N close-on-exec descriptors
- close_range() with and without CLOSE_RANGE_UNSHARE
Here are the medians over two boots which agree within 2%. With
/dev/null as the file and 4096, 65536 and 1M descriptors:
4096 fds 65536 fds 1M fds
----------------------------------------------------------------------
exit 1045 -> 895 us 18.9 -> 14.9 ms 429 -> 306 ms
-14.4% -21.2% -28.7%
----------------------------------------------------------------------
execve, close-on-exec 1393 -> 1190 us 21.4 -> 16.9 ms 471 -> 325 ms
-14.6% -21.0% -31.1%
----------------------------------------------------------------------
close_range() 1179 -> 988 us 21.9 -> 17.4 ms 484 -> 337 ms
-16.1% -20.6% -30.3%
----------------------------------------------------------------------
close_range(UNSHARE) 1118 -> 958 us 22.1 -> 17.8 ms 487 -> 333 ms
-14.3% -19.8% -31.7%
So that's a 16-21% win or 40-150 ns per closed file. This sheds
init_task_work() and task_work_add() per file with its cmpxchg() and the
TIF_NOTIFY_RESUME test-and-set, the list walk with the indirect call
in the path out, and then a second pass over every struct file that
isn't cache-hot anymore by then.
The performance boost holds from 256 to 1M descriptors and is largest
at 1M. Note the thread count doesn't matter since one thread does the
close anyway.
So with eight processes tearing down 65536 descriptors each at the same
time the win is 16-21% (34.9 -> 29.5 ms for exit, 34.4 -> 27.0 ms
for execve). The old code walks the table once to queue the task work
and then walks all 16 MB of struct file a second time from
task_work_run().
So once eight CPUs compete for the cache that second pass gets
very expensive. With 32 or 64 processes at once both kernels are bound
by pushing millions of files through slab and RCU. So then the
difference shrinks to 4-8%. Still though...
Files whose own release dominates the cleanup gain a little less. 13%
for eventfds, 11% for pipes, 6% for AF_UNIX sockets and 7% for distinct
tmpfs inodes. For them a release costs about 2 us per descriptor anyway.
Using function profiling a 65536 descriptor exit spends 4.6 ms in
exit_files() and 11.1 ms in task_work_run() before and now 13.7 ms in
exit_files() and nothing in task_work_run() after.
close(2) is untouched and measures the same. will-it-scale (open1,
open3, dup1, eventfd1, unix1, pipe1, signal1, processes and threads)
is within 2% either way. dup1_threads at 16 tasks and getppid1 at 64
tasks a few percent better which I'd put down to layout.
* patches from https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-0-a5c1800dc930@kernel.org:
coredump: factor out coredump_wait_inactive()
coredump: replace the startup completion with a thread count
sched: add wait_var_event_state()
coredump: drop core_state->dumper
fs: make close_cloexec_files() synchronous
fs: rename do_close_on_exec() to close_cloexec_files()
fs: make close_range() synchronous
fs: make close_files() synchronous
fs: add filp_close_sync()
fs: remove unshare_files()
fs: move unshare_fd() to fs/file.c
fs: add switch_files_struct()
fs: don't open-code file_close_fd() in close_fd()
Link: https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-0-a5c1800dc930@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
commit
97f08aa822
|
|
@ -1875,7 +1875,7 @@ static int fill_note_info(struct elfhdr *elf, int phdrs,
|
|||
return 0;
|
||||
|
||||
info->thread->task = dump_task;
|
||||
for (ct = dump_task->signal->core_state->dumper.next; ct; ct = ct->next) {
|
||||
for (ct = dump_task->signal->core_state->tasks; ct; ct = ct->next) {
|
||||
t = kzalloc_flex(*t, notes, info->thread_notes);
|
||||
if (unlikely(!t))
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -1504,7 +1504,7 @@ static int elf_fdpic_core_dump(struct coredump_params *cprm)
|
|||
if (!psinfo)
|
||||
goto end_coredump;
|
||||
|
||||
for (ct = current->signal->core_state->dumper.next;
|
||||
for (ct = current->signal->core_state->tasks;
|
||||
ct; ct = ct->next) {
|
||||
tmp = elf_dump_thread_status(cprm->siginfo->si_signo,
|
||||
ct->task, &thread_status_size);
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
#include <linux/oom.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/wait_bit.h>
|
||||
#include <linux/path.h>
|
||||
#include <linux/timekeeping.h>
|
||||
#include <linux/sysctl.h>
|
||||
|
|
@ -514,38 +515,38 @@ static int zap_threads(struct task_struct *tsk,
|
|||
nr = zap_process(signal, exit_code);
|
||||
clear_tsk_thread_flag(tsk, TIF_SIGPENDING);
|
||||
tsk->flags |= PF_DUMPCORE;
|
||||
atomic_set(&core_state->nr_threads, nr);
|
||||
atomic_set(&core_state->threads_remaining, nr);
|
||||
}
|
||||
spin_unlock_irq(&tsk->sighand->siglock);
|
||||
return nr;
|
||||
}
|
||||
|
||||
static void coredump_wait_inactive(struct core_state *core_state)
|
||||
{
|
||||
struct core_thread *ptr;
|
||||
|
||||
wait_var_event_state(&core_state->threads_remaining,
|
||||
!atomic_read_acquire(&core_state->threads_remaining),
|
||||
TASK_UNINTERRUPTIBLE | TASK_FREEZABLE);
|
||||
/*
|
||||
* Wait for all the threads to become inactive, so that
|
||||
* all the thread context (extended register state, like
|
||||
* fpu etc) gets copied to the memory.
|
||||
*/
|
||||
for (ptr = core_state->tasks; ptr; ptr = ptr->next)
|
||||
wait_task_inactive(ptr->task, TASK_ANY);
|
||||
}
|
||||
|
||||
static int coredump_wait(int exit_code, struct core_state *core_state)
|
||||
{
|
||||
struct task_struct *tsk = current;
|
||||
int core_waiters = -EBUSY;
|
||||
|
||||
init_completion(&core_state->startup);
|
||||
core_state->dumper.task = tsk;
|
||||
core_state->dumper.next = NULL;
|
||||
core_state->tasks = NULL;
|
||||
|
||||
core_waiters = zap_threads(tsk, core_state, exit_code);
|
||||
if (core_waiters > 0) {
|
||||
struct core_thread *ptr;
|
||||
|
||||
wait_for_completion_state(&core_state->startup,
|
||||
TASK_UNINTERRUPTIBLE|TASK_FREEZABLE);
|
||||
/*
|
||||
* Wait for all the threads to become inactive, so that
|
||||
* all the thread context (extended register state, like
|
||||
* fpu etc) gets copied to the memory.
|
||||
*/
|
||||
ptr = core_state->dumper.next;
|
||||
while (ptr != NULL) {
|
||||
wait_task_inactive(ptr->task, TASK_ANY);
|
||||
ptr = ptr->next;
|
||||
}
|
||||
}
|
||||
if (core_waiters > 0)
|
||||
coredump_wait_inactive(core_state);
|
||||
|
||||
return core_waiters;
|
||||
}
|
||||
|
|
@ -558,7 +559,7 @@ static void coredump_finish(bool core_dumped)
|
|||
spin_lock_irq(¤t->sighand->siglock);
|
||||
if (core_dumped && !__fatal_signal_pending(current))
|
||||
current->signal->group_exit_code |= 0x80;
|
||||
next = current->signal->core_state->dumper.next;
|
||||
next = current->signal->core_state->tasks;
|
||||
current->signal->core_state = NULL;
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
|
||||
|
|
|
|||
13
fs/exec.c
13
fs/exec.c
|
|
@ -1124,6 +1124,7 @@ static struct file *bprm_identity_file(const struct linux_binprm *bprm)
|
|||
int begin_new_exec(struct linux_binprm * bprm)
|
||||
{
|
||||
struct task_struct *me = current;
|
||||
struct files_struct *files = NULL;
|
||||
int retval;
|
||||
|
||||
/* A pending PT_INTERP substitution this format cannot consume. */
|
||||
|
|
@ -1160,9 +1161,11 @@ int begin_new_exec(struct linux_binprm * bprm)
|
|||
io_uring_task_cancel();
|
||||
|
||||
/* Ensure the files table is not shared. */
|
||||
retval = unshare_files();
|
||||
retval = unshare_fd(CLONE_FILES, &files);
|
||||
if (retval)
|
||||
goto out;
|
||||
if (files)
|
||||
switch_files_struct(me, files);
|
||||
|
||||
/*
|
||||
* We have to apply CLOEXEC before we change whether the process is
|
||||
|
|
@ -1170,13 +1173,13 @@ int begin_new_exec(struct linux_binprm * bprm)
|
|||
* trying to access the should-be-closed file descriptors of a process
|
||||
* undergoing exec(2).
|
||||
*
|
||||
* This can block on filesystem ->flush() handlers, including waiting
|
||||
* for FUSE daemons, so do it before exec_mmap takes the
|
||||
* exec_update_lock.
|
||||
* This can block on filesystem ->flush() and ->release() handlers,
|
||||
* including waiting for FUSE daemons, so do it before exec_mmap
|
||||
* takes the exec_update_lock.
|
||||
* This must happen after the point of no return, and after unsharing
|
||||
* the FD table.
|
||||
*/
|
||||
do_close_on_exec(me->files);
|
||||
close_cloexec_files(me->files);
|
||||
|
||||
/*
|
||||
* Must be called _before_ exec_mmap() as bprm->mm is
|
||||
|
|
|
|||
58
fs/file.c
58
fs/file.c
|
|
@ -531,7 +531,25 @@ struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *range)
|
|||
return newf;
|
||||
}
|
||||
|
||||
static struct fdtable *close_files(struct files_struct * files)
|
||||
/*
|
||||
* Unshare file descriptor table if it is being shared
|
||||
*/
|
||||
int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp)
|
||||
{
|
||||
struct files_struct *fd = current->files;
|
||||
|
||||
if ((unshare_flags & CLONE_FILES) &&
|
||||
(fd && atomic_read(&fd->count) > 1)) {
|
||||
fd = dup_fd(fd, NULL);
|
||||
if (IS_ERR(fd))
|
||||
return PTR_ERR(fd);
|
||||
*new_fdp = fd;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct fdtable *close_files(struct files_struct *files)
|
||||
{
|
||||
/*
|
||||
* It is safe to dereference the fd table without RCU or
|
||||
|
|
@ -551,7 +569,7 @@ static struct fdtable *close_files(struct files_struct * files)
|
|||
if (set & 1) {
|
||||
struct file *file = fdt->fd[i];
|
||||
if (file) {
|
||||
filp_close(file, files);
|
||||
filp_close_sync(file, files);
|
||||
cond_resched();
|
||||
}
|
||||
}
|
||||
|
|
@ -575,16 +593,18 @@ void put_files_struct(struct files_struct *files)
|
|||
}
|
||||
}
|
||||
|
||||
/* Install @files on @tsk, consuming the reference, and put the old table. */
|
||||
void switch_files_struct(struct task_struct *tsk, struct files_struct *files)
|
||||
{
|
||||
scoped_guard(task_lock, tsk)
|
||||
swap(tsk->files, files);
|
||||
put_files_struct(files);
|
||||
}
|
||||
|
||||
void exit_files(struct task_struct *tsk)
|
||||
{
|
||||
struct files_struct * files = tsk->files;
|
||||
|
||||
if (files) {
|
||||
task_lock(tsk);
|
||||
tsk->files = NULL;
|
||||
task_unlock(tsk);
|
||||
put_files_struct(files);
|
||||
}
|
||||
if (tsk->files)
|
||||
switch_files_struct(tsk, NULL);
|
||||
}
|
||||
|
||||
struct files_struct init_files = {
|
||||
|
|
@ -792,16 +812,13 @@ struct file *file_close_fd_locked(struct files_struct *files, unsigned fd)
|
|||
|
||||
int close_fd(unsigned fd)
|
||||
{
|
||||
struct files_struct *files = current->files;
|
||||
struct file *file;
|
||||
|
||||
spin_lock(&files->file_lock);
|
||||
file = file_close_fd_locked(files, fd);
|
||||
spin_unlock(&files->file_lock);
|
||||
file = file_close_fd(fd);
|
||||
if (!file)
|
||||
return -EBADF;
|
||||
|
||||
return filp_close(file, files);
|
||||
return filp_close(file, current->files);
|
||||
}
|
||||
EXPORT_SYMBOL(close_fd);
|
||||
|
||||
|
|
@ -894,7 +911,7 @@ static inline void __range_close(struct files_struct *files,
|
|||
file = file_close_fd_locked(files, fd);
|
||||
if (file) {
|
||||
spin_unlock(&files->file_lock);
|
||||
filp_close(file, files);
|
||||
filp_close_sync(file, files);
|
||||
cond_resched();
|
||||
spin_lock(&files->file_lock);
|
||||
fdt = files_fdtable(files);
|
||||
|
|
@ -986,10 +1003,7 @@ SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd,
|
|||
* We're done closing the files we were supposed to. Time to install
|
||||
* the new file descriptor table and drop the old one.
|
||||
*/
|
||||
task_lock(me);
|
||||
me->files = cur_fds;
|
||||
task_unlock(me);
|
||||
put_files_struct(fds);
|
||||
switch_files_struct(me, cur_fds);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -1015,7 +1029,7 @@ struct file *file_close_fd(unsigned int fd)
|
|||
return file;
|
||||
}
|
||||
|
||||
void do_close_on_exec(struct files_struct *files)
|
||||
void close_cloexec_files(struct files_struct *files)
|
||||
{
|
||||
unsigned i;
|
||||
struct fdtable *fdt;
|
||||
|
|
@ -1042,7 +1056,7 @@ void do_close_on_exec(struct files_struct *files)
|
|||
rcu_assign_pointer(fdt->fd[fd], NULL);
|
||||
__put_unused_fd(files, fd);
|
||||
spin_unlock(&files->file_lock);
|
||||
filp_close(file, files);
|
||||
filp_close_sync(file, files);
|
||||
cond_resched();
|
||||
spin_lock(&files->file_lock);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ extern struct file *do_file_open_root(const struct path *,
|
|||
extern struct open_how build_open_how(int flags, umode_t mode);
|
||||
extern int build_open_flags(const struct open_how *how, struct open_flags *op);
|
||||
struct file *file_close_fd_locked(struct files_struct *files, unsigned fd);
|
||||
int filp_close_sync(struct file *filp, fl_owner_t id);
|
||||
|
||||
int do_ftruncate(struct file *file, loff_t length, unsigned int flags);
|
||||
int chmod_common(const struct path *path, umode_t mode);
|
||||
|
|
|
|||
17
fs/open.c
17
fs/open.c
|
|
@ -1537,6 +1537,19 @@ int filp_close(struct file *filp, fl_owner_t id)
|
|||
}
|
||||
EXPORT_SYMBOL(filp_close);
|
||||
|
||||
/* Like filp_close() but the last reference is put right here. */
|
||||
int filp_close_sync(struct file *filp, fl_owner_t id)
|
||||
{
|
||||
int retval;
|
||||
|
||||
/* Kernel threads must never put their final reference here. */
|
||||
VFS_WARN_ON_ONCE(current->flags & PF_KTHREAD);
|
||||
retval = filp_flush(filp, id);
|
||||
fput_close_sync(filp);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Careful here! We test whether the file pointer is NULL before
|
||||
* releasing the fd. This ensures that one clone task can't release
|
||||
|
|
@ -1551,13 +1564,11 @@ SYSCALL_DEFINE1(close, unsigned int, fd)
|
|||
if (!file)
|
||||
return -EBADF;
|
||||
|
||||
retval = filp_flush(file, current->files);
|
||||
|
||||
/*
|
||||
* We're returning to user space. Don't bother
|
||||
* with any delayed fput() cases.
|
||||
*/
|
||||
fput_close_sync(file);
|
||||
retval = filp_close_sync(file, current->files);
|
||||
|
||||
if (likely(retval == 0))
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -100,7 +100,8 @@ static inline bool close_on_exec(unsigned int fd, const struct files_struct *fil
|
|||
struct task_struct;
|
||||
|
||||
void put_files_struct(struct files_struct *fs);
|
||||
int unshare_files(void);
|
||||
void switch_files_struct(struct task_struct *tsk, struct files_struct *files);
|
||||
int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp);
|
||||
enum fd_range_flags {
|
||||
/* Leave behind all descriptors outside of the specified range. */
|
||||
FD_RANGE_EXCEPT = (1U << 0),
|
||||
|
|
@ -114,7 +115,7 @@ struct fd_range {
|
|||
enum fd_range_flags flags;
|
||||
};
|
||||
struct files_struct *dup_fd(struct files_struct *, struct fd_range *) __latent_entropy;
|
||||
void do_close_on_exec(struct files_struct *);
|
||||
void close_cloexec_files(struct files_struct *);
|
||||
int iterate_fd(struct files_struct *, unsigned,
|
||||
int (*)(const void *, struct file *, unsigned),
|
||||
const void *);
|
||||
|
|
|
|||
|
|
@ -79,9 +79,9 @@ struct core_thread {
|
|||
};
|
||||
|
||||
struct core_state {
|
||||
atomic_t nr_threads;
|
||||
struct core_thread dumper;
|
||||
struct completion startup;
|
||||
/* Threads the dumper still waits for. */
|
||||
atomic_t threads_remaining;
|
||||
struct core_thread *tasks;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -432,6 +432,32 @@ do { \
|
|||
__ret; \
|
||||
})
|
||||
|
||||
/**
|
||||
* wait_var_event_state - wait for a variable to be updated and notified
|
||||
* @var: the address of variable being waited on
|
||||
* @condition: the condition to wait for
|
||||
* @state: the task state to sleep in, %TASK_UNINTERRUPTIBLE etc.
|
||||
*
|
||||
* Wait for a @condition to be true, only re-checking when a wake up is
|
||||
* received for the given @var (an arbitrary kernel address which need
|
||||
* not be directly related to the given condition, but usually is).
|
||||
*
|
||||
* Returns 0 if the condition became true, or %-ERESTARTSYS if a signal
|
||||
* arrived which @state allows to interrupt.
|
||||
*
|
||||
* The condition should normally use smp_load_acquire() or a similarly
|
||||
* ordered access to ensure that any changes to memory made before the
|
||||
* condition became true will be visible after the wait completes.
|
||||
*/
|
||||
#define wait_var_event_state(var, condition, state) \
|
||||
({ \
|
||||
int __ret = 0; \
|
||||
might_sleep(); \
|
||||
if (!(condition)) \
|
||||
__ret = ___wait_var_event(var, condition, (state), 0, 0, schedule()); \
|
||||
__ret; \
|
||||
})
|
||||
|
||||
/**
|
||||
* wait_var_event_any_lock - wait for a variable to be updated under a lock
|
||||
* @var: the address of the variable being waited on
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/wait_bit.h>
|
||||
#include <linux/personality.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/iocontext.h>
|
||||
|
|
@ -435,15 +436,14 @@ static void coredump_task_exit(struct task_struct *tsk,
|
|||
|
||||
self.task = tsk;
|
||||
if (self.task->flags & PF_SIGNALED)
|
||||
self.next = xchg(&core_state->dumper.next, &self);
|
||||
self.next = xchg(&core_state->tasks, &self);
|
||||
else
|
||||
self.task = NULL;
|
||||
/*
|
||||
* Implies mb(), the result of xchg() must be visible
|
||||
* to core_state->dumper.
|
||||
* to the dumper.
|
||||
*/
|
||||
if (atomic_dec_and_test(&core_state->nr_threads))
|
||||
complete(&core_state->startup);
|
||||
atomic_dec_and_wake_up(&core_state->threads_remaining);
|
||||
|
||||
for (;;) {
|
||||
set_current_state(TASK_IDLE|TASK_FREEZABLE);
|
||||
|
|
@ -917,7 +917,7 @@ static void synchronize_group_exit(struct task_struct *tsk, long code)
|
|||
* Serialize with any possible pending coredump.
|
||||
* We must hold siglock around checking core_state
|
||||
* and setting PF_POSTCOREDUMP. The core-inducing thread
|
||||
* will increment ->nr_threads for each thread in the
|
||||
* will increment ->threads_remaining for each thread in the
|
||||
* group without PF_POSTCOREDUMP set.
|
||||
*/
|
||||
tsk->flags |= PF_POSTCOREDUMP;
|
||||
|
|
|
|||
|
|
@ -3210,24 +3210,6 @@ static int unshare_fs(unsigned long unshare_flags, struct fs_struct **new_fsp)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unshare file descriptor table if it is being shared
|
||||
*/
|
||||
static int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp)
|
||||
{
|
||||
struct files_struct *fd = current->files;
|
||||
|
||||
if ((unshare_flags & CLONE_FILES) &&
|
||||
(fd && atomic_read(&fd->count) > 1)) {
|
||||
fd = dup_fd(fd, NULL);
|
||||
if (IS_ERR(fd))
|
||||
return PTR_ERR(fd);
|
||||
*new_fdp = fd;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* unshare allows a process to 'unshare' part of the process
|
||||
* context which was originally shared using clone. copy_*
|
||||
|
|
@ -3323,10 +3305,8 @@ int ksys_unshare(unsigned long unshare_flags)
|
|||
if (new_fs)
|
||||
new_fs = switch_fs_struct(new_fs);
|
||||
|
||||
if (new_fd) {
|
||||
guard(task_lock)(current);
|
||||
swap(current->files, new_fd);
|
||||
}
|
||||
if (new_fd)
|
||||
switch_files_struct(current, no_free_ptr(new_fd));
|
||||
|
||||
if (new_cred) {
|
||||
/* Install the new user namespace */
|
||||
|
|
@ -3359,30 +3339,6 @@ SYSCALL_DEFINE1(unshare, unsigned long, unshare_flags)
|
|||
return ksys_unshare(unshare_flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper to unshare the files of the current task.
|
||||
* We don't want to expose copy_files internals to
|
||||
* the exec layer of the kernel.
|
||||
*/
|
||||
|
||||
int unshare_files(void)
|
||||
{
|
||||
struct task_struct *task = current;
|
||||
struct files_struct *old, *copy = NULL;
|
||||
int error;
|
||||
|
||||
error = unshare_fd(CLONE_FILES, ©);
|
||||
if (error || !copy)
|
||||
return error;
|
||||
|
||||
old = task->files;
|
||||
task_lock(task);
|
||||
task->files = copy;
|
||||
task_unlock(task);
|
||||
put_files_struct(old);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sysctl_max_threads(const struct ctl_table *table, int write,
|
||||
void *buffer, size_t *lenp, loff_t *ppos)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user