diff options
Diffstat (limited to 'fs/fcntl.c')
-rw-r--r-- | fs/fcntl.c | 184 |
1 files changed, 69 insertions, 115 deletions
diff --git a/fs/fcntl.c b/fs/fcntl.c index 330a7d782591..ac4f7db9f134 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c | |||
@@ -49,145 +49,94 @@ static int get_close_on_exec(unsigned int fd) | |||
49 | return res; | 49 | return res; |
50 | } | 50 | } |
51 | 51 | ||
52 | /* | 52 | asmlinkage long sys_dup3(unsigned int oldfd, unsigned int newfd, int flags) |
53 | * locate_fd finds a free file descriptor in the open_fds fdset, | ||
54 | * expanding the fd arrays if necessary. Must be called with the | ||
55 | * file_lock held for write. | ||
56 | */ | ||
57 | |||
58 | static int locate_fd(unsigned int orig_start, int cloexec) | ||
59 | { | ||
60 | struct files_struct *files = current->files; | ||
61 | unsigned int newfd; | ||
62 | unsigned int start; | ||
63 | int error; | ||
64 | struct fdtable *fdt; | ||
65 | |||
66 | spin_lock(&files->file_lock); | ||
67 | |||
68 | error = -EINVAL; | ||
69 | if (orig_start >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur) | ||
70 | goto out; | ||
71 | |||
72 | repeat: | ||
73 | fdt = files_fdtable(files); | ||
74 | /* | ||
75 | * Someone might have closed fd's in the range | ||
76 | * orig_start..fdt->next_fd | ||
77 | */ | ||
78 | start = orig_start; | ||
79 | if (start < files->next_fd) | ||
80 | start = files->next_fd; | ||
81 | |||
82 | newfd = start; | ||
83 | if (start < fdt->max_fds) | ||
84 | newfd = find_next_zero_bit(fdt->open_fds->fds_bits, | ||
85 | fdt->max_fds, start); | ||
86 | |||
87 | error = -EMFILE; | ||
88 | if (newfd >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur) | ||
89 | goto out; | ||
90 | |||
91 | error = expand_files(files, newfd); | ||
92 | if (error < 0) | ||
93 | goto out; | ||
94 | |||
95 | /* | ||
96 | * If we needed to expand the fs array we | ||
97 | * might have blocked - try again. | ||
98 | */ | ||
99 | if (error) | ||
100 | goto repeat; | ||
101 | |||
102 | if (start <= files->next_fd) | ||
103 | files->next_fd = newfd + 1; | ||
104 | |||
105 | FD_SET(newfd, fdt->open_fds); | ||
106 | if (cloexec) | ||
107 | FD_SET(newfd, fdt->close_on_exec); | ||
108 | else | ||
109 | FD_CLR(newfd, fdt->close_on_exec); | ||
110 | error = newfd; | ||
111 | |||
112 | out: | ||
113 | spin_unlock(&files->file_lock); | ||
114 | return error; | ||
115 | } | ||
116 | |||
117 | static int dupfd(struct file *file, unsigned int start, int cloexec) | ||
118 | { | ||
119 | int fd = locate_fd(start, cloexec); | ||
120 | if (fd >= 0) | ||
121 | fd_install(fd, file); | ||
122 | else | ||
123 | fput(file); | ||
124 | |||
125 | return fd; | ||
126 | } | ||
127 | |||
128 | asmlinkage long sys_dup2(unsigned int oldfd, unsigned int newfd) | ||
129 | { | 53 | { |
130 | int err = -EBADF; | 54 | int err = -EBADF; |
131 | struct file * file, *tofree; | 55 | struct file * file, *tofree; |
132 | struct files_struct * files = current->files; | 56 | struct files_struct * files = current->files; |
133 | struct fdtable *fdt; | 57 | struct fdtable *fdt; |
134 | 58 | ||
135 | spin_lock(&files->file_lock); | 59 | if ((flags & ~O_CLOEXEC) != 0) |
136 | if (!(file = fcheck(oldfd))) | 60 | return -EINVAL; |
137 | goto out_unlock; | ||
138 | err = newfd; | ||
139 | if (newfd == oldfd) | ||
140 | goto out_unlock; | ||
141 | err = -EBADF; | ||
142 | if (newfd >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur) | ||
143 | goto out_unlock; | ||
144 | get_file(file); /* We are now finished with oldfd */ | ||
145 | |||
146 | err = expand_files(files, newfd); | ||
147 | if (err < 0) | ||
148 | goto out_fput; | ||
149 | 61 | ||
150 | /* To avoid races with open() and dup(), we will mark the fd as | 62 | if (unlikely(oldfd == newfd)) |
151 | * in-use in the open-file bitmap throughout the entire dup2() | 63 | return -EINVAL; |
152 | * process. This is quite safe: do_close() uses the fd array | ||
153 | * entry, not the bitmap, to decide what work needs to be | ||
154 | * done. --sct */ | ||
155 | /* Doesn't work. open() might be there first. --AV */ | ||
156 | 64 | ||
157 | /* Yes. It's a race. In user space. Nothing sane to do */ | 65 | spin_lock(&files->file_lock); |
66 | err = expand_files(files, newfd); | ||
67 | file = fcheck(oldfd); | ||
68 | if (unlikely(!file)) | ||
69 | goto Ebadf; | ||
70 | if (unlikely(err < 0)) { | ||
71 | if (err == -EMFILE) | ||
72 | goto Ebadf; | ||
73 | goto out_unlock; | ||
74 | } | ||
75 | /* | ||
76 | * We need to detect attempts to do dup2() over allocated but still | ||
77 | * not finished descriptor. NB: OpenBSD avoids that at the price of | ||
78 | * extra work in their equivalent of fget() - they insert struct | ||
79 | * file immediately after grabbing descriptor, mark it larval if | ||
80 | * more work (e.g. actual opening) is needed and make sure that | ||
81 | * fget() treats larval files as absent. Potentially interesting, | ||
82 | * but while extra work in fget() is trivial, locking implications | ||
83 | * and amount of surgery on open()-related paths in VFS are not. | ||
84 | * FreeBSD fails with -EBADF in the same situation, NetBSD "solution" | ||
85 | * deadlocks in rather amusing ways, AFAICS. All of that is out of | ||
86 | * scope of POSIX or SUS, since neither considers shared descriptor | ||
87 | * tables and this condition does not arise without those. | ||
88 | */ | ||
158 | err = -EBUSY; | 89 | err = -EBUSY; |
159 | fdt = files_fdtable(files); | 90 | fdt = files_fdtable(files); |
160 | tofree = fdt->fd[newfd]; | 91 | tofree = fdt->fd[newfd]; |
161 | if (!tofree && FD_ISSET(newfd, fdt->open_fds)) | 92 | if (!tofree && FD_ISSET(newfd, fdt->open_fds)) |
162 | goto out_fput; | 93 | goto out_unlock; |
163 | 94 | get_file(file); | |
164 | rcu_assign_pointer(fdt->fd[newfd], file); | 95 | rcu_assign_pointer(fdt->fd[newfd], file); |
165 | FD_SET(newfd, fdt->open_fds); | 96 | FD_SET(newfd, fdt->open_fds); |
166 | FD_CLR(newfd, fdt->close_on_exec); | 97 | if (flags & O_CLOEXEC) |
98 | FD_SET(newfd, fdt->close_on_exec); | ||
99 | else | ||
100 | FD_CLR(newfd, fdt->close_on_exec); | ||
167 | spin_unlock(&files->file_lock); | 101 | spin_unlock(&files->file_lock); |
168 | 102 | ||
169 | if (tofree) | 103 | if (tofree) |
170 | filp_close(tofree, files); | 104 | filp_close(tofree, files); |
171 | err = newfd; | 105 | |
172 | out: | 106 | return newfd; |
173 | return err; | 107 | |
108 | Ebadf: | ||
109 | err = -EBADF; | ||
174 | out_unlock: | 110 | out_unlock: |
175 | spin_unlock(&files->file_lock); | 111 | spin_unlock(&files->file_lock); |
176 | goto out; | 112 | return err; |
113 | } | ||
177 | 114 | ||
178 | out_fput: | 115 | asmlinkage long sys_dup2(unsigned int oldfd, unsigned int newfd) |
179 | spin_unlock(&files->file_lock); | 116 | { |
180 | fput(file); | 117 | if (unlikely(newfd == oldfd)) { /* corner case */ |
181 | goto out; | 118 | struct files_struct *files = current->files; |
119 | rcu_read_lock(); | ||
120 | if (!fcheck_files(files, oldfd)) | ||
121 | oldfd = -EBADF; | ||
122 | rcu_read_unlock(); | ||
123 | return oldfd; | ||
124 | } | ||
125 | return sys_dup3(oldfd, newfd, 0); | ||
182 | } | 126 | } |
183 | 127 | ||
184 | asmlinkage long sys_dup(unsigned int fildes) | 128 | asmlinkage long sys_dup(unsigned int fildes) |
185 | { | 129 | { |
186 | int ret = -EBADF; | 130 | int ret = -EBADF; |
187 | struct file * file = fget(fildes); | 131 | struct file *file = fget(fildes); |
188 | 132 | ||
189 | if (file) | 133 | if (file) { |
190 | ret = dupfd(file, 0, 0); | 134 | ret = get_unused_fd(); |
135 | if (ret >= 0) | ||
136 | fd_install(ret, file); | ||
137 | else | ||
138 | fput(file); | ||
139 | } | ||
191 | return ret; | 140 | return ret; |
192 | } | 141 | } |
193 | 142 | ||
@@ -310,8 +259,13 @@ static long do_fcntl(int fd, unsigned int cmd, unsigned long arg, | |||
310 | switch (cmd) { | 259 | switch (cmd) { |
311 | case F_DUPFD: | 260 | case F_DUPFD: |
312 | case F_DUPFD_CLOEXEC: | 261 | case F_DUPFD_CLOEXEC: |
313 | get_file(filp); | 262 | if (arg >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur) |
314 | err = dupfd(filp, arg, cmd == F_DUPFD_CLOEXEC); | 263 | break; |
264 | err = alloc_fd(arg, cmd == F_DUPFD_CLOEXEC ? O_CLOEXEC : 0); | ||
265 | if (err >= 0) { | ||
266 | get_file(filp); | ||
267 | fd_install(err, filp); | ||
268 | } | ||
315 | break; | 269 | break; |
316 | case F_GETFD: | 270 | case F_GETFD: |
317 | err = get_close_on_exec(fd) ? FD_CLOEXEC : 0; | 271 | err = get_close_on_exec(fd) ? FD_CLOEXEC : 0; |