vfs_preopen: implement SMB_VFS_OPENAT()
[samba.git] / source3 / modules / vfs_preopen.c
1 /*
2  * Force a readahead of files by opening them and reading the first bytes
3  *
4  * Copyright (C) Volker Lendecke 2008
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20
21 #include "includes.h"
22 #include "system/filesys.h"
23 #include "smbd/smbd.h"
24 #include "lib/util/sys_rw.h"
25 #include "lib/util/sys_rw_data.h"
26
27 struct preopen_state;
28
29 struct preopen_helper {
30         struct preopen_state *state;
31         struct tevent_fd *fde;
32         pid_t pid;
33         int fd;
34         bool busy;
35 };
36
37 struct preopen_state {
38         int num_helpers;
39         struct preopen_helper *helpers;
40
41         size_t to_read;         /* How many bytes to read in children? */
42         int queue_max;
43
44         char *template_fname;   /* Filename to be sent to children */
45         size_t number_start;    /* start offset into "template_fname" */
46         int num_digits;         /* How many digits is the number long? */
47
48         int fnum_sent;          /* last fname sent to children */
49
50         int fnum_queue_end;     /* last fname to be sent, based on
51                                  * last open call + preopen:queuelen
52                                  */
53
54         name_compare_entry *preopen_names;
55 };
56
57 static void preopen_helper_destroy(struct preopen_helper *c)
58 {
59         int status;
60         TALLOC_FREE(c->fde);
61         close(c->fd);
62         c->fd = -1;
63         kill(c->pid, SIGKILL);
64         waitpid(c->pid, &status, 0);
65         c->busy = true;
66 }
67
68 static void preopen_queue_run(struct preopen_state *state)
69 {
70         char *pdelimiter;
71         char delimiter;
72
73         pdelimiter = state->template_fname + state->number_start
74                 + state->num_digits;
75         delimiter = *pdelimiter;
76
77         while (state->fnum_sent < state->fnum_queue_end) {
78
79                 ssize_t written;
80                 size_t to_write;
81                 int helper;
82
83                 for (helper=0; helper<state->num_helpers; helper++) {
84                         if (state->helpers[helper].busy) {
85                                 continue;
86                         }
87                         break;
88                 }
89                 if (helper == state->num_helpers) {
90                         /* everyone is busy */
91                         return;
92                 }
93
94                 snprintf(state->template_fname + state->number_start,
95                          state->num_digits + 1,
96                          "%.*lu", state->num_digits,
97                          (long unsigned int)(state->fnum_sent + 1));
98                 *pdelimiter = delimiter;
99
100                 to_write = talloc_get_size(state->template_fname);
101                 written = write_data(state->helpers[helper].fd,
102                                      state->template_fname, to_write);
103                 state->helpers[helper].busy = true;
104
105                 if (written != to_write) {
106                         preopen_helper_destroy(&state->helpers[helper]);
107                 }
108                 state->fnum_sent += 1;
109         }
110 }
111
112 static void preopen_helper_readable(struct tevent_context *ev,
113                                     struct tevent_fd *fde, uint16_t flags,
114                                     void *priv)
115 {
116         struct preopen_helper *helper = (struct preopen_helper *)priv;
117         struct preopen_state *state = helper->state;
118         ssize_t nread;
119         char c;
120
121         if ((flags & TEVENT_FD_READ) == 0) {
122                 return;
123         }
124
125         nread = read(helper->fd, &c, 1);
126         if (nread <= 0) {
127                 preopen_helper_destroy(helper);
128                 return;
129         }
130
131         helper->busy = false;
132
133         preopen_queue_run(state);
134 }
135
136 static int preopen_helpers_destructor(struct preopen_state *c)
137 {
138         int i;
139
140         for (i=0; i<c->num_helpers; i++) {
141                 if (c->helpers[i].fd == -1) {
142                         continue;
143                 }
144                 preopen_helper_destroy(&c->helpers[i]);
145         }
146
147         return 0;
148 }
149
150 static bool preopen_helper_open_one(int sock_fd, char **pnamebuf,
151                                     size_t to_read, void *filebuf)
152 {
153         char *namebuf = *pnamebuf;
154         ssize_t nread;
155         char c = 0;
156         int fd;
157
158         nread = 0;
159
160         do {
161                 ssize_t thistime;
162
163                 thistime = read(sock_fd, namebuf + nread,
164                                 talloc_get_size(namebuf) - nread);
165                 if (thistime <= 0) {
166                         return false;
167                 }
168
169                 nread += thistime;
170
171                 if (nread == talloc_get_size(namebuf)) {
172                         namebuf = talloc_realloc(
173                                 NULL, namebuf, char,
174                                 talloc_get_size(namebuf) * 2);
175                         if (namebuf == NULL) {
176                                 return false;
177                         }
178                         *pnamebuf = namebuf;
179                 }
180         } while (namebuf[nread - 1] != '\0');
181
182         fd = open(namebuf, O_RDONLY);
183         if (fd == -1) {
184                 goto done;
185         }
186         nread = read(fd, filebuf, to_read);
187         close(fd);
188
189  done:
190         sys_write_v(sock_fd, &c, 1);
191         return true;
192 }
193
194 static bool preopen_helper(int fd, size_t to_read)
195 {
196         char *namebuf;
197         void *readbuf;
198
199         namebuf = talloc_array(NULL, char, 1024);
200         if (namebuf == NULL) {
201                 return false;
202         }
203
204         readbuf = talloc_size(NULL, to_read);
205         if (readbuf == NULL) {
206                 TALLOC_FREE(namebuf);
207                 return false;
208         }
209
210         while (preopen_helper_open_one(fd, &namebuf, to_read, readbuf)) {
211                 ;
212         }
213
214         TALLOC_FREE(readbuf);
215         TALLOC_FREE(namebuf);
216         return false;
217 }
218
219 static NTSTATUS preopen_init_helper(struct preopen_helper *h)
220 {
221         int fdpair[2];
222         NTSTATUS status;
223
224         if (socketpair(AF_UNIX, SOCK_STREAM, 0, fdpair) == -1) {
225                 status = map_nt_error_from_unix(errno);
226                 DEBUG(10, ("socketpair() failed: %s\n", strerror(errno)));
227                 return status;
228         }
229
230         h->pid = fork();
231
232         if (h->pid == -1) {
233                 return map_nt_error_from_unix(errno);
234         }
235
236         if (h->pid == 0) {
237                 close(fdpair[0]);
238                 preopen_helper(fdpair[1], h->state->to_read);
239                 exit(0);
240         }
241         close(fdpair[1]);
242         h->fd = fdpair[0];
243         h->fde = tevent_add_fd(global_event_context(), h->state, h->fd,
244                               TEVENT_FD_READ, preopen_helper_readable, h);
245         if (h->fde == NULL) {
246                 close(h->fd);
247                 h->fd = -1;
248                 return NT_STATUS_NO_MEMORY;
249         }
250         h->busy = false;
251         return NT_STATUS_OK;
252 }
253
254 static NTSTATUS preopen_init_helpers(TALLOC_CTX *mem_ctx, size_t to_read,
255                                      int num_helpers, int queue_max,
256                                      struct preopen_state **presult)
257 {
258         struct preopen_state *result;
259         int i;
260
261         result = talloc(mem_ctx, struct preopen_state);
262         if (result == NULL) {
263                 return NT_STATUS_NO_MEMORY;
264         }
265
266         result->num_helpers = num_helpers;
267         result->helpers = talloc_array(result, struct preopen_helper,
268                                        num_helpers);
269         if (result->helpers == NULL) {
270                 TALLOC_FREE(result);
271                 return NT_STATUS_NO_MEMORY;
272         }
273
274         result->to_read = to_read;
275         result->queue_max = queue_max;
276         result->template_fname = NULL;
277         result->fnum_sent = 0;
278         result->fnum_queue_end = 0;
279
280         for (i=0; i<num_helpers; i++) {
281                 result->helpers[i].state = result;
282                 result->helpers[i].fd = -1;
283         }
284
285         talloc_set_destructor(result, preopen_helpers_destructor);
286
287         for (i=0; i<num_helpers; i++) {
288                 preopen_init_helper(&result->helpers[i]);
289         }
290
291         *presult = result;
292         return NT_STATUS_OK;
293 }
294
295 static void preopen_free_helpers(void **ptr)
296 {
297         TALLOC_FREE(*ptr);
298 }
299
300 static struct preopen_state *preopen_state_get(vfs_handle_struct *handle)
301 {
302         struct preopen_state *state;
303         NTSTATUS status;
304         const char *namelist;
305
306         if (SMB_VFS_HANDLE_TEST_DATA(handle)) {
307                 SMB_VFS_HANDLE_GET_DATA(handle, state, struct preopen_state,
308                                         return NULL);
309                 return state;
310         }
311
312         namelist = lp_parm_const_string(SNUM(handle->conn), "preopen", "names",
313                                         NULL);
314
315         if (namelist == NULL) {
316                 return NULL;
317         }
318
319         status = preopen_init_helpers(
320                 NULL,
321                 lp_parm_int(SNUM(handle->conn), "preopen", "num_bytes", 1),
322                 lp_parm_int(SNUM(handle->conn), "preopen", "helpers", 1),
323                 lp_parm_int(SNUM(handle->conn), "preopen", "queuelen", 10),
324                 &state);
325         if (!NT_STATUS_IS_OK(status)) {
326                 return NULL;
327         }
328
329         set_namearray(&state->preopen_names, namelist);
330
331         if (state->preopen_names == NULL) {
332                 TALLOC_FREE(state);
333                 return NULL;
334         }
335
336         if (!SMB_VFS_HANDLE_TEST_DATA(handle)) {
337                 SMB_VFS_HANDLE_SET_DATA(handle, state, preopen_free_helpers,
338                                         struct preopen_state, return NULL);
339         }
340
341         return state;
342 }
343
344 static bool preopen_parse_fname(const char *fname, unsigned long *pnum,
345                                 size_t *pstart_idx, int *pnum_digits)
346 {
347         const char *p;
348         char *q = NULL;
349         unsigned long num;
350         int error = 0;
351
352         p = strrchr_m(fname, '/');
353         if (p == NULL) {
354                 p = fname;
355         }
356
357         p += 1;
358         while (p[0] != '\0') {
359                 if (isdigit(p[0]) && isdigit(p[1]) && isdigit(p[2])) {
360                         break;
361                 }
362                 p += 1;
363         }
364         if (*p == '\0') {
365                 /* no digits around */
366                 return false;
367         }
368
369         num = smb_strtoul(p, (char **)&q, 10, &error, SMB_STR_STANDARD);
370         if (error != 0) {
371                 return false;
372         }
373
374         if (num+1 < num) {
375                 /* overflow */
376                 return false;
377         }
378
379         *pnum = num;
380         *pstart_idx = (p - fname);
381         *pnum_digits = (q - p);
382         return true;
383 }
384
385 static int preopen_open(vfs_handle_struct *handle,
386                         struct smb_filename *smb_fname, files_struct *fsp,
387                         int flags, mode_t mode)
388 {
389         struct preopen_state *state;
390         int res;
391         unsigned long num;
392
393         DEBUG(10, ("preopen_open called on %s\n", smb_fname_str_dbg(smb_fname)));
394
395         state = preopen_state_get(handle);
396         if (state == NULL) {
397                 return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
398         }
399
400         res = SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
401         if (res == -1) {
402                 return -1;
403         }
404
405         if ((flags & O_ACCMODE) != O_RDONLY) {
406                 return res;
407         }
408
409         if (!is_in_path(smb_fname->base_name, state->preopen_names, true)) {
410                 DEBUG(10, ("%s does not match the preopen:names list\n",
411                            smb_fname_str_dbg(smb_fname)));
412                 return res;
413         }
414
415         TALLOC_FREE(state->template_fname);
416         state->template_fname = talloc_asprintf(
417                 state, "%s/%s",
418                 fsp->conn->cwd_fsp->fsp_name->base_name, smb_fname->base_name);
419
420         if (state->template_fname == NULL) {
421                 return res;
422         }
423
424         if (!preopen_parse_fname(state->template_fname, &num,
425                                  &state->number_start, &state->num_digits)) {
426                 TALLOC_FREE(state->template_fname);
427                 return res;
428         }
429
430         if (num > state->fnum_sent) {
431                 /*
432                  * Helpers were too slow, there's no point in reading
433                  * files in helpers that we already read in the
434                  * parent.
435                  */
436                 state->fnum_sent = num;
437         }
438
439         if ((state->fnum_queue_end != 0) /* Something was started earlier */
440             && (num < (state->fnum_queue_end - state->queue_max))) {
441                 /*
442                  * "num" is before the queue we announced. This means
443                  * a new run is started.
444                  */
445                 state->fnum_sent = num;
446         }
447
448         state->fnum_queue_end = num + state->queue_max;
449
450         preopen_queue_run(state);
451
452         return res;
453 }
454
455 static int preopen_openat(struct vfs_handle_struct *handle,
456                           const struct files_struct *dirfsp,
457                           const struct smb_filename *smb_fname,
458                           struct files_struct *fsp,
459                           int flags,
460                           mode_t mode)
461 {
462         struct preopen_state *state;
463         int res;
464         unsigned long num;
465
466         DEBUG(10, ("preopen_open called on %s\n", smb_fname_str_dbg(smb_fname)));
467
468         state = preopen_state_get(handle);
469         if (state == NULL) {
470                 return SMB_VFS_NEXT_OPENAT(handle,
471                                            dirfsp,
472                                            smb_fname,
473                                            fsp,
474                                            flags,
475                                            mode);
476         }
477
478         res = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, flags, mode);
479         if (res == -1) {
480                 return -1;
481         }
482
483         if ((flags & O_ACCMODE) != O_RDONLY) {
484                 return res;
485         }
486
487         if (!is_in_path(smb_fname->base_name, state->preopen_names, true)) {
488                 DEBUG(10, ("%s does not match the preopen:names list\n",
489                            smb_fname_str_dbg(smb_fname)));
490                 return res;
491         }
492
493         TALLOC_FREE(state->template_fname);
494         state->template_fname = talloc_asprintf(
495                 state, "%s/%s",
496                 dirfsp->fsp_name->base_name, smb_fname->base_name);
497
498         if (state->template_fname == NULL) {
499                 return res;
500         }
501
502         if (!preopen_parse_fname(state->template_fname, &num,
503                                  &state->number_start, &state->num_digits)) {
504                 TALLOC_FREE(state->template_fname);
505                 return res;
506         }
507
508         if (num > state->fnum_sent) {
509                 /*
510                  * Helpers were too slow, there's no point in reading
511                  * files in helpers that we already read in the
512                  * parent.
513                  */
514                 state->fnum_sent = num;
515         }
516
517         if ((state->fnum_queue_end != 0) /* Something was started earlier */
518             && (num < (state->fnum_queue_end - state->queue_max))) {
519                 /*
520                  * "num" is before the queue we announced. This means
521                  * a new run is started.
522                  */
523                 state->fnum_sent = num;
524         }
525
526         state->fnum_queue_end = num + state->queue_max;
527
528         preopen_queue_run(state);
529
530         return res;
531 }
532
533 static struct vfs_fn_pointers vfs_preopen_fns = {
534         .open_fn = preopen_open,
535         .openat_fn = preopen_openat,
536 };
537
538 static_decl_vfs;
539 NTSTATUS vfs_preopen_init(TALLOC_CTX *ctx)
540 {
541         return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
542                                 "preopen", &vfs_preopen_fns);
543 }