vfs: Use fsp_get_pathref_fd() in aio_pthread
[samba.git] / source3 / modules / vfs_aio_pthread.c
1 /*
2  * Simulate Posix AIO using pthreads.
3  *
4  * Based on the aio_fork work from Volker and Volker's pthreadpool library.
5  *
6  * Copyright (C) Volker Lendecke 2008
7  * Copyright (C) Jeremy Allison 2012
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22  */
23
24 #include "includes.h"
25 #include "system/filesys.h"
26 #include "system/shmem.h"
27 #include "smbd/smbd.h"
28 #include "smbd/globals.h"
29 #include "../lib/pthreadpool/pthreadpool_tevent.h"
30 #ifdef HAVE_LINUX_FALLOC_H
31 #include <linux/falloc.h>
32 #endif
33
34 #if defined(HAVE_OPENAT) && defined(HAVE_LINUX_THREAD_CREDENTIALS)
35
36 /*
37  * We must have openat() to do any thread-based
38  * asynchronous opens. We also must be using
39  * thread-specific credentials (Linux-only
40  * for now).
41  */
42
43 struct aio_open_private_data {
44         struct aio_open_private_data *prev, *next;
45         /* Inputs. */
46         int dir_fd;
47         bool opened_dir_fd;
48         int flags;
49         mode_t mode;
50         uint64_t mid;
51         bool in_progress;
52         struct smb_filename *fsp_name;
53         struct smb_filename *smb_fname;
54         connection_struct *conn;
55         struct smbXsrv_connection *xconn;
56         const struct security_unix_token *ux_tok;
57         uint64_t initial_allocation_size;
58         /* Returns. */
59         int ret_fd;
60         int ret_errno;
61 };
62
63 /* List of outstanding requests we have. */
64 static struct aio_open_private_data *open_pd_list;
65
66 static void aio_open_do(struct aio_open_private_data *opd);
67 static void opd_free(struct aio_open_private_data *opd);
68
69 /************************************************************************
70  Find the open private data by mid.
71 ***********************************************************************/
72
73 static struct aio_open_private_data *find_open_private_data_by_mid(uint64_t mid)
74 {
75         struct aio_open_private_data *opd;
76
77         for (opd = open_pd_list; opd != NULL; opd = opd->next) {
78                 if (opd->mid == mid) {
79                         return opd;
80                 }
81         }
82
83         return NULL;
84 }
85
86 /************************************************************************
87  Callback when an open completes.
88 ***********************************************************************/
89
90 static void aio_open_handle_completion(struct tevent_req *subreq)
91 {
92         struct aio_open_private_data *opd =
93                 tevent_req_callback_data(subreq,
94                 struct aio_open_private_data);
95         int ret;
96
97         ret = pthreadpool_tevent_job_recv(subreq);
98         TALLOC_FREE(subreq);
99
100         /*
101          * We're no longer in flight. Remove the
102          * destructor used to preserve opd so
103          * a talloc_free actually removes it.
104          */
105         talloc_set_destructor(opd, NULL);
106
107         if (opd->conn == NULL) {
108                 /*
109                  * We were shutdown closed in flight. No one
110                  * wants the result, and state has been reparented
111                  * to the NULL context, so just free it so we
112                  * don't leak memory.
113                  */
114                 DBG_NOTICE("aio open request for %s abandoned in flight\n",
115                         opd->fsp_name->base_name);
116                 if (opd->ret_fd != -1) {
117                         close(opd->ret_fd);
118                         opd->ret_fd = -1;
119                 }
120                 /*
121                  * Find outstanding event and reschedule so the client
122                  * gets an error message return from the open.
123                  */
124                 schedule_deferred_open_message_smb(opd->xconn, opd->mid);
125                 opd_free(opd);
126                 return;
127         }
128
129         if (ret != 0) {
130                 bool ok;
131
132                 if (ret != EAGAIN) {
133                         smb_panic("aio_open_handle_completion");
134                         /* notreached. */
135                         return;
136                 }
137                 /*
138                  * Make sure we run as the user again
139                  */
140                 ok = change_to_user_and_service(opd->conn, opd->conn->vuid);
141                 if (!ok) {
142                         smb_panic("Can't change to user");
143                         return;
144                 }
145                 /*
146                  * If we get EAGAIN from pthreadpool_tevent_job_recv() this
147                  * means the lower level pthreadpool failed to create a new
148                  * thread. Fallback to sync processing in that case to allow
149                  * some progress for the client.
150                  */
151                 aio_open_do(opd);
152         }
153
154         DEBUG(10,("aio_open_handle_completion: mid %llu "
155                 "for file %s completed\n",
156                 (unsigned long long)opd->mid,
157                 opd->fsp_name->base_name));
158
159         opd->in_progress = false;
160
161         /* Find outstanding event and reschedule. */
162         if (!schedule_deferred_open_message_smb(opd->xconn, opd->mid)) {
163                 /*
164                  * Outstanding event didn't exist or was
165                  * cancelled. Free up the fd and throw
166                  * away the result.
167                  */
168                 if (opd->ret_fd != -1) {
169                         close(opd->ret_fd);
170                         opd->ret_fd = -1;
171                 }
172                 opd_free(opd);
173         }
174 }
175
176 /*****************************************************************
177  The core of the async open code - the worker function. Note we
178  use the new openat() system call to avoid any problems with
179  current working directory changes plus we change credentials
180  on the thread to prevent any security race conditions.
181 *****************************************************************/
182
183 static void aio_open_worker(void *private_data)
184 {
185         struct aio_open_private_data *opd =
186                 (struct aio_open_private_data *)private_data;
187
188         /* Become the correct credential on this thread. */
189         if (set_thread_credentials(opd->ux_tok->uid,
190                                 opd->ux_tok->gid,
191                                 (size_t)opd->ux_tok->ngroups,
192                                 opd->ux_tok->groups) != 0) {
193                 opd->ret_fd = -1;
194                 opd->ret_errno = errno;
195                 return;
196         }
197
198         aio_open_do(opd);
199 }
200
201 static void aio_open_do(struct aio_open_private_data *opd)
202 {
203         opd->ret_fd = openat(opd->dir_fd,
204                         opd->smb_fname->base_name,
205                         opd->flags,
206                         opd->mode);
207
208         if (opd->ret_fd == -1) {
209                 opd->ret_errno = errno;
210         } else {
211                 /* Create was successful. */
212                 opd->ret_errno = 0;
213
214 #if defined(HAVE_LINUX_FALLOCATE)
215                 /*
216                  * See if we can set the initial
217                  * allocation size. We don't record
218                  * the return for this as it's an
219                  * optimization - the upper layer
220                  * will also do this for us once
221                  * the open returns.
222                  */
223                 if (opd->initial_allocation_size) {
224                         (void)fallocate(opd->ret_fd,
225                                         FALLOC_FL_KEEP_SIZE,
226                                         0,
227                                         (off_t)opd->initial_allocation_size);
228                 }
229 #endif
230         }
231 }
232
233 /************************************************************************
234  Open private data teardown.
235 ***********************************************************************/
236
237 static void opd_free(struct aio_open_private_data *opd)
238 {
239         if (opd->opened_dir_fd && opd->dir_fd != -1) {
240                 close(opd->dir_fd);
241         }
242         DLIST_REMOVE(open_pd_list, opd);
243         TALLOC_FREE(opd);
244 }
245
246 /************************************************************************
247  Create and initialize a private data struct for async open.
248 ***********************************************************************/
249
250 static struct aio_open_private_data *create_private_open_data(
251         TALLOC_CTX *ctx,
252         const struct files_struct *dirfsp,
253         const struct smb_filename *smb_fname,
254         const files_struct *fsp,
255         int flags,
256         mode_t mode)
257 {
258         struct aio_open_private_data *opd = talloc_zero(ctx,
259                                         struct aio_open_private_data);
260
261         if (!opd) {
262                 return NULL;
263         }
264
265         *opd = (struct aio_open_private_data) {
266                 .dir_fd = -1,
267                 .ret_fd = -1,
268                 .ret_errno = EINPROGRESS,
269                 .flags = flags,
270                 .mode = mode,
271                 .mid = fsp->mid,
272                 .in_progress = true,
273                 .conn = fsp->conn,
274                 /*
275                  * TODO: In future we need a proper algorithm
276                  * to find the correct connection for a fsp.
277                  * For now we only have one connection, so this is correct...
278                  */
279                 .xconn = fsp->conn->sconn->client->connections,
280                 .initial_allocation_size = fsp->initial_allocation_size,
281         };
282
283         /* Copy our current credentials. */
284         opd->ux_tok = copy_unix_token(opd, get_current_utok(fsp->conn));
285         if (opd->ux_tok == NULL) {
286                 opd_free(opd);
287                 return NULL;
288         }
289
290         /*
291          * Copy the full fsp_name and smb_fname which is the basename.
292          */
293         opd->smb_fname = cp_smb_filename(opd, smb_fname);
294         if (opd->smb_fname == NULL) {
295                 opd_free(opd);
296                 return NULL;
297         }
298
299         opd->fsp_name = cp_smb_filename(opd, fsp->fsp_name);
300         if (opd->fsp_name == NULL) {
301                 opd_free(opd);
302                 return NULL;
303         }
304
305         if (fsp_get_pathref_fd(dirfsp) != AT_FDCWD) {
306                 opd->dir_fd = fsp_get_pathref_fd(dirfsp);
307         } else {
308 #if defined(O_DIRECTORY)
309                 opd->dir_fd = open(".", O_RDONLY|O_DIRECTORY);
310 #else
311                 opd->dir_fd = open(".", O_RDONLY);
312 #endif
313                 opd->opened_dir_fd = true;
314         }
315         if (opd->dir_fd == -1) {
316                 opd_free(opd);
317                 return NULL;
318         }
319
320         DLIST_ADD_END(open_pd_list, opd);
321         return opd;
322 }
323
324 static int opd_inflight_destructor(struct aio_open_private_data *opd)
325 {
326         /*
327          * Setting conn to NULL allows us to
328          * discover the connection was torn
329          * down which kills the fsp that owns
330          * opd.
331          */
332         DBG_NOTICE("aio open request for %s cancelled\n",
333                 opd->fsp_name->base_name);
334         opd->conn = NULL;
335         /* Don't let opd go away. */
336         return -1;
337 }
338
339 /*****************************************************************
340  Setup an async open.
341 *****************************************************************/
342
343 static int open_async(const struct files_struct *dirfsp,
344                       const struct smb_filename *smb_fname,
345                       const files_struct *fsp,
346                       int flags,
347                       mode_t mode)
348 {
349         struct aio_open_private_data *opd = NULL;
350         struct tevent_req *subreq = NULL;
351
352         /*
353          * Allocate off fsp->conn, not NULL or fsp. As we're going
354          * async fsp will get talloc_free'd when we return
355          * EINPROGRESS/NT_STATUS_MORE_PROCESSING_REQUIRED. A new fsp
356          * pointer gets allocated on every re-run of the
357          * open code path. Allocating on fsp->conn instead
358          * of NULL allows use to get notified via destructor
359          * if the conn is force-closed or we shutdown.
360          * opd is always safely freed in all codepath so no
361          * memory leaks.
362          */
363         opd = create_private_open_data(fsp->conn,
364                                        dirfsp,
365                                        smb_fname,
366                                        fsp,
367                                        flags,
368                                        mode);
369         if (opd == NULL) {
370                 DEBUG(10, ("open_async: Could not create private data.\n"));
371                 return -1;
372         }
373
374         subreq = pthreadpool_tevent_job_send(opd,
375                                              fsp->conn->sconn->ev_ctx,
376                                              fsp->conn->sconn->pool,
377                                              aio_open_worker, opd);
378         if (subreq == NULL) {
379                 opd_free(opd);
380                 return -1;
381         }
382         tevent_req_set_callback(subreq, aio_open_handle_completion, opd);
383
384         DEBUG(5,("open_async: mid %llu created for file %s\n",
385                 (unsigned long long)opd->mid,
386                 opd->fsp_name->base_name));
387
388         /*
389          * Add a destructor to protect us from connection
390          * teardown whilst the open thread is in flight.
391          */
392         talloc_set_destructor(opd, opd_inflight_destructor);
393
394         /* Cause the calling code to reschedule us. */
395         errno = EINPROGRESS; /* Maps to NT_STATUS_MORE_PROCESSING_REQUIRED. */
396         return -1;
397 }
398
399 /*****************************************************************
400  Look for a matching SMB2 mid. If we find it we're rescheduled,
401  just return the completed open.
402 *****************************************************************/
403
404 static bool find_completed_open(files_struct *fsp,
405                                 int *p_fd,
406                                 int *p_errno)
407 {
408         struct aio_open_private_data *opd;
409
410         opd = find_open_private_data_by_mid(fsp->mid);
411         if (!opd) {
412                 return false;
413         }
414
415         if (opd->in_progress) {
416                 DEBUG(0,("find_completed_open: mid %llu "
417                         "still in progress for "
418                         "file %s. PANIC !\n",
419                         (unsigned long long)opd->mid,
420                         opd->fsp_name->base_name));
421                 /* Disaster ! This is an open timeout. Just panic. */
422                 smb_panic("find_completed_open - in_progress\n");
423                 /* notreached. */
424                 return false;
425         }
426
427         *p_fd = opd->ret_fd;
428         *p_errno = opd->ret_errno;
429
430         DEBUG(5,("find_completed_open: mid %llu returning "
431                 "fd = %d, errno = %d (%s) "
432                 "for file %s\n",
433                 (unsigned long long)opd->mid,
434                 opd->ret_fd,
435                 opd->ret_errno,
436                 strerror(opd->ret_errno),
437                 smb_fname_str_dbg(fsp->fsp_name)));
438
439         /* Now we can free the opd. */
440         opd_free(opd);
441         return true;
442 }
443
444 /*****************************************************************
445  The core open function. Only go async on O_CREAT|O_EXCL
446  opens to prevent any race conditions.
447 *****************************************************************/
448
449 static int aio_pthread_openat_fn(vfs_handle_struct *handle,
450                                  const struct files_struct *dirfsp,
451                                  const struct smb_filename *smb_fname,
452                                  struct files_struct *fsp,
453                                  int flags,
454                                  mode_t mode)
455 {
456         int my_errno = 0;
457         int fd = -1;
458         bool aio_allow_open = lp_parm_bool(
459                 SNUM(handle->conn), "aio_pthread", "aio open", false);
460
461         if (smb_fname->stream_name != NULL) {
462                 /* Don't handle stream opens. */
463                 errno = ENOENT;
464                 return -1;
465         }
466
467         if (fsp->conn->sconn->client->server_multi_channel_enabled) {
468                 /*
469                  * This module is not compatible with multi channel yet.
470                  */
471                 aio_allow_open = false;
472         }
473
474         if (!aio_allow_open) {
475                 /* aio opens turned off. */
476                 return openat(fsp_get_pathref_fd(dirfsp),
477                               smb_fname->base_name,
478                               flags,
479                               mode);
480         }
481
482         if (!(flags & O_CREAT)) {
483                 /* Only creates matter. */
484                 return openat(fsp_get_pathref_fd(dirfsp),
485                               smb_fname->base_name,
486                               flags,
487                               mode);
488         }
489
490         if (!(flags & O_EXCL)) {
491                 /* Only creates with O_EXCL matter. */
492                 return openat(fsp_get_pathref_fd(dirfsp),
493                               smb_fname->base_name,
494                               flags,
495                               mode);
496         }
497
498         /*
499          * See if this is a reentrant call - i.e. is this a
500          * restart of an existing open that just completed.
501          */
502
503         if (find_completed_open(fsp,
504                                 &fd,
505                                 &my_errno)) {
506                 errno = my_errno;
507                 return fd;
508         }
509
510         /* Ok, it's a create exclusive call - pass it to a thread helper. */
511         return open_async(dirfsp, smb_fname, fsp, flags, mode);
512 }
513 #endif
514
515 static struct vfs_fn_pointers vfs_aio_pthread_fns = {
516 #if defined(HAVE_OPENAT) && defined(HAVE_LINUX_THREAD_CREDENTIALS)
517         .openat_fn = aio_pthread_openat_fn,
518 #endif
519 };
520
521 static_decl_vfs;
522 NTSTATUS vfs_aio_pthread_init(TALLOC_CTX *ctx)
523 {
524         return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
525                                 "aio_pthread", &vfs_aio_pthread_fns);
526 }