lib: Split out write_data[_iov]
[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/sys_rw_data.h"
25
26 struct preopen_state;
27
28 struct preopen_helper {
29         struct preopen_state *state;
30         struct tevent_fd *fde;
31         pid_t pid;
32         int fd;
33         bool busy;
34 };
35
36 struct preopen_state {
37         int num_helpers;
38         struct preopen_helper *helpers;
39
40         size_t to_read;         /* How many bytes to read in children? */
41         int queue_max;
42
43         char *template_fname;   /* Filename to be sent to children */
44         size_t number_start;    /* start offset into "template_fname" */
45         int num_digits;         /* How many digits is the number long? */
46
47         int fnum_sent;          /* last fname sent to children */
48
49         int fnum_queue_end;     /* last fname to be sent, based on
50                                  * last open call + preopen:queuelen
51                                  */
52
53         name_compare_entry *preopen_names;
54 };
55
56 static void preopen_helper_destroy(struct preopen_helper *c)
57 {
58         int status;
59         close(c->fd);
60         c->fd = -1;
61         kill(c->pid, SIGKILL);
62         waitpid(c->pid, &status, 0);
63         c->busy = true;
64 }
65
66 static void preopen_queue_run(struct preopen_state *state)
67 {
68         char *pdelimiter;
69         char delimiter;
70
71         pdelimiter = state->template_fname + state->number_start
72                 + state->num_digits;
73         delimiter = *pdelimiter;
74
75         while (state->fnum_sent < state->fnum_queue_end) {
76
77                 ssize_t written;
78                 size_t to_write;
79                 int helper;
80
81                 for (helper=0; helper<state->num_helpers; helper++) {
82                         if (state->helpers[helper].busy) {
83                                 continue;
84                         }
85                         break;
86                 }
87                 if (helper == state->num_helpers) {
88                         /* everyone is busy */
89                         return;
90                 }
91
92                 snprintf(state->template_fname + state->number_start,
93                          state->num_digits + 1,
94                          "%.*lu", state->num_digits,
95                          (long unsigned int)(state->fnum_sent + 1));
96                 *pdelimiter = delimiter;
97
98                 to_write = talloc_get_size(state->template_fname);
99                 written = write_data(state->helpers[helper].fd,
100                                      state->template_fname, to_write);
101                 state->helpers[helper].busy = true;
102
103                 if (written != to_write) {
104                         preopen_helper_destroy(&state->helpers[helper]);
105                 }
106                 state->fnum_sent += 1;
107         }
108 }
109
110 static void preopen_helper_readable(struct tevent_context *ev,
111                                     struct tevent_fd *fde, uint16_t flags,
112                                     void *priv)
113 {
114         struct preopen_helper *helper = (struct preopen_helper *)priv;
115         struct preopen_state *state = helper->state;
116         ssize_t nread;
117         char c;
118
119         if ((flags & TEVENT_FD_READ) == 0) {
120                 return;
121         }
122
123         nread = read(helper->fd, &c, 1);
124         if (nread <= 0) {
125                 preopen_helper_destroy(helper);
126                 return;
127         }
128
129         helper->busy = false;
130
131         preopen_queue_run(state);
132 }
133
134 static int preopen_helpers_destructor(struct preopen_state *c)
135 {
136         int i;
137
138         for (i=0; i<c->num_helpers; i++) {
139                 if (c->helpers[i].fd == -1) {
140                         continue;
141                 }
142                 preopen_helper_destroy(&c->helpers[i]);
143         }
144
145         return 0;
146 }
147
148 static bool preopen_helper_open_one(int sock_fd, char **pnamebuf,
149                                     size_t to_read, void *filebuf)
150 {
151         char *namebuf = *pnamebuf;
152         ssize_t nwritten, nread;
153         char c = 0;
154         int fd;
155
156         nread = 0;
157
158         while ((nread == 0) || (namebuf[nread-1] != '\0')) {
159                 ssize_t thistime;
160
161                 thistime = read(sock_fd, namebuf + nread,
162                                 talloc_get_size(namebuf) - nread);
163                 if (thistime <= 0) {
164                         return false;
165                 }
166
167                 nread += thistime;
168
169                 if (nread == talloc_get_size(namebuf)) {
170                         namebuf = talloc_realloc(
171                                 NULL, namebuf, char,
172                                 talloc_get_size(namebuf) * 2);
173                         if (namebuf == NULL) {
174                                 return false;
175                         }
176                         *pnamebuf = namebuf;
177                 }
178         }
179
180         fd = open(namebuf, O_RDONLY);
181         if (fd == -1) {
182                 goto done;
183         }
184         nread = read(fd, filebuf, to_read);
185         close(fd);
186
187  done:
188         nwritten = write(sock_fd, &c, 1);
189         return true;
190 }
191
192 static bool preopen_helper(int fd, size_t to_read)
193 {
194         char *namebuf;
195         void *readbuf;
196
197         namebuf = talloc_array(NULL, char, 1024);
198         if (namebuf == NULL) {
199                 return false;
200         }
201
202         readbuf = talloc_size(NULL, to_read);
203         if (readbuf == NULL) {
204                 TALLOC_FREE(namebuf);
205                 return false;
206         }
207
208         while (preopen_helper_open_one(fd, &namebuf, to_read, readbuf)) {
209                 ;
210         }
211
212         TALLOC_FREE(readbuf);
213         TALLOC_FREE(namebuf);
214         return false;
215 }
216
217 static NTSTATUS preopen_init_helper(struct preopen_helper *h)
218 {
219         int fdpair[2];
220         NTSTATUS status;
221
222         if (socketpair(AF_UNIX, SOCK_STREAM, 0, fdpair) == -1) {
223                 status = map_nt_error_from_unix(errno);
224                 DEBUG(10, ("socketpair() failed: %s\n", strerror(errno)));
225                 return status;
226         }
227
228         h->pid = fork();
229
230         if (h->pid == -1) {
231                 return map_nt_error_from_unix(errno);
232         }
233
234         if (h->pid == 0) {
235                 close(fdpair[0]);
236                 preopen_helper(fdpair[1], h->state->to_read);
237                 exit(0);
238         }
239         close(fdpair[1]);
240         h->fd = fdpair[0];
241         h->fde = tevent_add_fd(server_event_context(), h->state, h->fd,
242                               TEVENT_FD_READ, preopen_helper_readable, h);
243         if (h->fde == NULL) {
244                 close(h->fd);
245                 h->fd = -1;
246                 return NT_STATUS_NO_MEMORY;
247         }
248         h->busy = false;
249         return NT_STATUS_OK;
250 }
251
252 static NTSTATUS preopen_init_helpers(TALLOC_CTX *mem_ctx, size_t to_read,
253                                      int num_helpers, int queue_max,
254                                      struct preopen_state **presult)
255 {
256         struct preopen_state *result;
257         int i;
258
259         result = talloc(mem_ctx, struct preopen_state);
260         if (result == NULL) {
261                 return NT_STATUS_NO_MEMORY;
262         }
263
264         result->num_helpers = num_helpers;
265         result->helpers = talloc_array(result, struct preopen_helper,
266                                        num_helpers);
267         if (result->helpers == NULL) {
268                 TALLOC_FREE(result);
269                 return NT_STATUS_NO_MEMORY;
270         }
271
272         result->to_read = to_read;
273         result->queue_max = queue_max;
274         result->template_fname = NULL;
275         result->fnum_sent = 0;
276
277         for (i=0; i<num_helpers; i++) {
278                 result->helpers[i].state = result;
279                 result->helpers[i].fd = -1;
280         }
281
282         talloc_set_destructor(result, preopen_helpers_destructor);
283
284         for (i=0; i<num_helpers; i++) {
285                 preopen_init_helper(&result->helpers[i]);
286         }
287
288         *presult = result;
289         return NT_STATUS_OK;
290 }
291
292 static void preopen_free_helpers(void **ptr)
293 {
294         TALLOC_FREE(*ptr);
295 }
296
297 static struct preopen_state *preopen_state_get(vfs_handle_struct *handle)
298 {
299         struct preopen_state *state;
300         NTSTATUS status;
301         const char *namelist;
302
303         if (SMB_VFS_HANDLE_TEST_DATA(handle)) {
304                 SMB_VFS_HANDLE_GET_DATA(handle, state, struct preopen_state,
305                                         return NULL);
306                 return state;
307         }
308
309         namelist = lp_parm_const_string(SNUM(handle->conn), "preopen", "names",
310                                         NULL);
311
312         if (namelist == NULL) {
313                 return NULL;
314         }
315
316         status = preopen_init_helpers(
317                 NULL,
318                 lp_parm_int(SNUM(handle->conn), "preopen", "num_bytes", 1),
319                 lp_parm_int(SNUM(handle->conn), "preopen", "helpers", 1),
320                 lp_parm_int(SNUM(handle->conn), "preopen", "queuelen", 10),
321                 &state);
322         if (!NT_STATUS_IS_OK(status)) {
323                 return NULL;
324         }
325
326         set_namearray(&state->preopen_names, namelist);
327
328         if (state->preopen_names == NULL) {
329                 TALLOC_FREE(state);
330                 return NULL;
331         }
332
333         if (!SMB_VFS_HANDLE_TEST_DATA(handle)) {
334                 SMB_VFS_HANDLE_SET_DATA(handle, state, preopen_free_helpers,
335                                         struct preopen_state, return NULL);
336         }
337
338         return state;
339 }
340
341 static bool preopen_parse_fname(const char *fname, unsigned long *pnum,
342                                 size_t *pstart_idx, int *pnum_digits)
343 {
344         const char *p, *q;
345         unsigned long num;
346
347         p = strrchr_m(fname, '/');
348         if (p == NULL) {
349                 p = fname;
350         }
351
352         p += 1;
353         while (p[0] != '\0') {
354                 if (isdigit(p[0]) && isdigit(p[1]) && isdigit(p[2])) {
355                         break;
356                 }
357                 p += 1;
358         }
359         if (*p == '\0') {
360                 /* no digits around */
361                 return false;
362         }
363
364         num = strtoul(p, (char **)&q, 10);
365
366         if (num+1 < num) {
367                 /* overflow */
368                 return false;
369         }
370
371         *pnum = num;
372         *pstart_idx = (p - fname);
373         *pnum_digits = (q - p);
374         return true;
375 }
376
377 static int preopen_open(vfs_handle_struct *handle,
378                         struct smb_filename *smb_fname, files_struct *fsp,
379                         int flags, mode_t mode)
380 {
381         struct preopen_state *state;
382         int res;
383         unsigned long num;
384
385         DEBUG(10, ("preopen_open called on %s\n", smb_fname_str_dbg(smb_fname)));
386
387         state = preopen_state_get(handle);
388         if (state == NULL) {
389                 return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
390         }
391
392         res = SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
393         if (res == -1) {
394                 return -1;
395         }
396
397         if (flags != O_RDONLY) {
398                 return res;
399         }
400
401         if (!is_in_path(smb_fname->base_name, state->preopen_names, true)) {
402                 DEBUG(10, ("%s does not match the preopen:names list\n",
403                            smb_fname_str_dbg(smb_fname)));
404                 return res;
405         }
406
407         TALLOC_FREE(state->template_fname);
408         state->template_fname = talloc_asprintf(
409                 state, "%s/%s", fsp->conn->cwd, smb_fname->base_name);
410
411         if (state->template_fname == NULL) {
412                 return res;
413         }
414
415         if (!preopen_parse_fname(state->template_fname, &num,
416                                  &state->number_start, &state->num_digits)) {
417                 TALLOC_FREE(state->template_fname);
418                 return res;
419         }
420
421         if (num > state->fnum_sent) {
422                 /*
423                  * Helpers were too slow, there's no point in reading
424                  * files in helpers that we already read in the
425                  * parent.
426                  */
427                 state->fnum_sent = num;
428         }
429
430         if ((state->fnum_queue_end != 0) /* Something was started earlier */
431             && (num < (state->fnum_queue_end - state->queue_max))) {
432                 /*
433                  * "num" is before the queue we announced. This means
434                  * a new run is started.
435                  */
436                 state->fnum_sent = num;
437         }
438
439         state->fnum_queue_end = num + state->queue_max;
440
441         preopen_queue_run(state);
442
443         return res;
444 }
445
446 static struct vfs_fn_pointers vfs_preopen_fns = {
447         .open_fn = preopen_open
448 };
449
450 NTSTATUS vfs_preopen_init(void);
451 NTSTATUS vfs_preopen_init(void)
452 {
453         return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
454                                 "preopen", &vfs_preopen_fns);
455 }