s3-prefork: implement prefork framework
[idra/samba.git] / source3 / lib / server_prefork.c
1 /*
2    Unix SMB/CIFS implementation.
3    Common server globals
4
5    Copyright (C) Simo Sorce <idra@samba.org> 2011
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "includes.h"
22 #include "system/time.h"
23 #include "system/shmem.h"
24 #include "system/filesys.h"
25 #include "server_prefork.h"
26 #include "../lib/util/util.h"
27
28 struct prefork_pool {
29
30         int listen_fd;
31         int lock_fd;
32
33         prefork_main_fn_t *main_fn;
34         void *private_data;
35
36         int pool_size;
37         struct pf_worker_data *pool;
38 };
39
40 int prefork_pool_destructor(struct prefork_pool *pfp)
41 {
42         munmap(pfp->pool, pfp->pool_size * sizeof(struct pf_worker_data));
43         return 0;
44 }
45
46 bool prefork_create_pool(struct tevent_context *ev_ctx,
47                          TALLOC_CTX *mem_ctx, int listen_fd,
48                          int min_children, int max_children,
49                          prefork_main_fn_t *main_fn, void *private_data,
50                          struct prefork_pool **pf_pool)
51 {
52         struct prefork_pool *pfp;
53         pid_t pid;
54         time_t now = time(NULL);
55         size_t data_size;
56         int ret;
57         int i;
58
59         pfp = talloc(mem_ctx, struct prefork_pool);
60         if (!pfp) {
61                 DEBUG(1, ("Out of memory!\n"));
62                 return false;
63         }
64         pfp->listen_fd = listen_fd;
65         pfp->main_fn = main_fn;
66         pfp->private_data = private_data;
67
68         pfp->lock_fd = create_unlink_tmp(NULL);
69         if (pfp->lock_fd == -1) {
70                 DEBUG(1, ("Failed to create prefork lock fd!\n"));
71                 talloc_free(pfp);
72                 return false;
73         }
74
75         pfp->pool_size = max_children;
76         data_size = sizeof(struct pf_worker_data) * max_children;
77
78         pfp->pool = mmap(NULL, data_size, PROT_READ|PROT_WRITE,
79                          MAP_SHARED|MAP_ANONYMOUS, -1, 0);
80         if (pfp->pool == MAP_FAILED) {
81                 DEBUG(1, ("Failed to mmap memory for prefork pool!\n"));
82                 talloc_free(pfp);
83                 return false;
84         }
85         talloc_set_destructor(pfp, prefork_pool_destructor);
86
87         for (i = 0; i < min_children; i++) {
88                 pid = sys_fork();
89                 switch (pid) {
90                 case -1:
91                         DEBUG(1, ("Failed to prefork child n. %d !\n", i));
92                         break;
93
94                 case 0: /* THE CHILD */
95
96                         pfp->pool[i].status = PF_WORKER_IDLE;
97
98                         ret = pfp->main_fn(ev_ctx, &pfp->pool[i],
99                                            pfp->listen_fd, pfp->lock_fd,
100                                            pfp->private_data);
101                         exit(ret);
102
103                 default: /* THE PARENT */
104                         pfp->pool[i].pid = pid;
105                         pfp->pool[i].started = now;
106                         break;
107                 }
108         }
109
110         *pf_pool = pfp;
111         return true;
112 }
113
114 int prefork_add_children(struct tevent_context *ev_ctx,
115                          struct prefork_pool *pfp,
116                          int num_children)
117 {
118         pid_t pid;
119         time_t now = time(NULL);
120         int ret;
121         int i, j;
122
123         for (i = 0, j = 0; i < pfp->pool_size && j < num_children; i++) {
124
125                 if (pfp->pool[i].status != PF_WORKER_NONE) {
126                         continue;
127                 }
128
129                 pid = sys_fork();
130                 switch (pid) {
131                 case -1:
132                         DEBUG(1, ("Failed to prefork child n. %d !\n", j));
133                         break;
134
135                 case 0: /* THE CHILD */
136
137                         pfp->pool[i].status = PF_WORKER_IDLE;
138                         ret = pfp->main_fn(ev_ctx, &pfp->pool[i],
139                                            pfp->listen_fd, pfp->lock_fd,
140                                            pfp->private_data);
141
142                         pfp->pool[i].status = PF_WORKER_EXITING;
143                         exit(ret);
144
145                 default: /* THE PARENT */
146                         pfp->pool[i].pid = pid;
147                         pfp->pool[i].started = now;
148                         j++;
149                         break;
150                 }
151         }
152
153         DEBUG(5, ("Added %d children!\n", j));
154
155         return j;
156 }
157
158 struct prefork_oldest {
159         int num;
160         time_t started;
161 };
162
163 /* sort in inverse order */
164 static int prefork_sort_oldest(const void *ap, const void *bp)
165 {
166         struct prefork_oldest *a = (struct prefork_oldest *)ap;
167         struct prefork_oldest *b = (struct prefork_oldest *)bp;
168
169         if (a->started == b->started) {
170                 return 0;
171         }
172         if (a->started < b->started) {
173                 return 1;
174         }
175         return -1;
176 }
177
178 int prefork_retire_children(struct prefork_pool *pfp,
179                             int num_children, time_t age_limit)
180 {
181         time_t now = time(NULL);
182         struct prefork_oldest *oldest;
183         int i, j;
184
185         oldest = talloc_array(pfp, struct prefork_oldest, pfp->pool_size);
186         if (!oldest) {
187                 return -1;
188         }
189
190         for (i = 0; i < pfp->pool_size; i++) {
191                 oldest[i].num = i;
192                 if (pfp->pool[i].status == PF_WORKER_IDLE) {
193                         oldest[i].started = pfp->pool[i].started;
194                 } else {
195                         oldest[i].started = now;
196                 }
197         }
198
199         qsort(oldest, pfp->pool_size,
200                 sizeof(struct prefork_oldest),
201                 prefork_sort_oldest);
202
203         for (i = 0, j = 0; i < pfp->pool_size && j < num_children; i++) {
204                 if (pfp->pool[i].status == PF_WORKER_IDLE &&
205                     pfp->pool[i].started <= age_limit) {
206                         /* tell the child it's time to give up */
207                         DEBUG(5, ("Retiring pid %d!\n", pfp->pool[i].pid));
208                         pfp->pool[i].cmds = PF_SRV_MSG_EXIT;
209                         kill(pfp->pool[i].pid, SIGHUP);
210                         j++;
211                 }
212         }
213
214         return j;
215 }
216
217 int prefork_count_active_children(struct prefork_pool *pfp, int *total)
218 {
219         int i, a, t;
220
221         a = 0;
222         t = 0;
223         for (i = 0; i < pfp->pool_size; i++) {
224                 if (pfp->pool[i].status == PF_WORKER_NONE) {
225                         continue;
226                 }
227
228                 t++;
229
230                 if (pfp->pool[i].num_clients == 0) {
231                         continue;
232                 }
233
234                 a++;
235         }
236
237         *total = t;
238         return a;
239 }
240
241 /* to be used to finally mark a children as dead, so that it's slot can
242  * be reused */
243 bool prefork_mark_pid_dead(struct prefork_pool *pfp, pid_t pid)
244 {
245         int i;
246
247         for (i = 0; i < pfp->pool_size; i++) {
248                 if (pfp->pool[i].pid == pid) {
249                         if (pfp->pool[i].status != PF_WORKER_EXITING) {
250                                 DEBUG(2, ("pid %d terminated abnormally!\n",
251                                           (int)pid));
252                         }
253
254                         /* reset all fields,
255                          * this makes status = PF_WORK_NONE */
256                         memset(&pfp->pool[i], 0,
257                                 sizeof(struct pf_worker_data));
258
259                         return true;
260                 }
261         }
262
263         return false;
264 }
265
266 /* ==== Functions used by children ==== */
267
268 static SIG_ATOMIC_T pf_alarm;
269
270 static void pf_alarm_cb(int signum)
271 {
272         pf_alarm = 1;
273 }
274
275
276 /*
277  * Parameters:
278  * pf - the worker shared data structure
279  * lock_fd - the file descriptor used for locking
280  * timeout - expressed in seconds:
281  *              -1 never timeouts,
282  *              0 timeouts immediately
283  *              N seconds before timing out
284  *
285  * Returns values:
286  * negative errno on fatal error
287  * 0 on success to acquire lock
288  * -1 on timeout/lock held by other
289  * -2 on server msg to terminate
290  * ERRNO on other errors
291  */
292
293 static int prefork_grab_lock(struct pf_worker_data *pf,
294                              int lock_fd, int timeout)
295 {
296         struct flock lock;
297         int op;
298         int ret;
299
300         if (pf->cmds == PF_SRV_MSG_EXIT) {
301                 return -2;
302         }
303
304         pf_alarm = 0;
305
306         if (timeout > 0) {
307                 CatchSignal(SIGALRM, pf_alarm_cb);
308                 alarm(timeout);
309         }
310
311         if (timeout == 0) {
312                 op = F_SETLK;
313         } else {
314                 op = F_SETLKW;
315         }
316
317         ret = 0;
318         do {
319                 ZERO_STRUCT(lock);
320                 lock.l_type = F_WRLCK;
321                 lock.l_whence = SEEK_SET;
322
323                 ret = fcntl(lock_fd, op, &lock);
324                 if (ret == 0) break;
325
326                 ret = errno;
327
328                 if (pf->cmds == PF_SRV_MSG_EXIT) {
329                         ret = -2;
330                         goto done;
331                 }
332
333                 switch (ret) {
334                 case EINTR:
335                         break;
336
337                 case EACCES:
338                 case EAGAIN:
339                         /* lock held by other proc */
340                         ret = -1;
341                         goto done;
342                 default:
343                         goto done;
344                 }
345
346                 if (pf_alarm == 1) {
347                         /* timed out */
348                         ret = -1;
349                         goto done;
350                 }
351         } while (timeout != 0);
352
353         if (ret != 0) {
354                 /* We have the Lock */
355                 pf->status = PF_WORKER_ACCEPTING;
356         }
357
358 done:
359         if (timeout > 0) {
360                 alarm(0);
361                 CatchSignal(SIGALRM, SIG_IGN);
362         }
363
364         if (ret > 0) {
365                 DEBUG(1, ("Failed to get lock (%d, %s)!\n",
366                           ret, strerror(ret)));
367         }
368         return ret;
369 }
370
371 /*
372  * Parameters:
373  * pf - the worker shared data structure
374  * lock_fd - the file descriptor used for locking
375  * timeout - expressed in seconds:
376  *              -1 never timeouts,
377  *              0 timeouts immediately
378  *              N seconds before timing out
379  *
380  * Returns values:
381  * negative errno on fatal error
382  * 0 on success to release lock
383  * -1 on timeout
384  * ERRNO on error
385  */
386
387 static int prefork_release_lock(struct pf_worker_data *pf,
388                                 int lock_fd, int timeout)
389 {
390         struct flock lock;
391         int op;
392         int ret;
393
394         pf_alarm = 0;
395
396         if (timeout > 0) {
397                 CatchSignal(SIGALRM, pf_alarm_cb);
398                 alarm(timeout);
399         }
400
401         if (timeout == 0) {
402                 op = F_SETLK;
403         } else {
404                 op = F_SETLKW;
405         }
406
407         do {
408                 ZERO_STRUCT(lock);
409                 lock.l_type = F_UNLCK;
410                 lock.l_whence = SEEK_SET;
411
412                 ret = fcntl(lock_fd, op, &lock);
413                 if (ret == 0) break;
414
415                 ret = errno;
416
417                 if (ret != EINTR) {
418                         goto done;
419                 }
420
421                 if (pf_alarm == 1) {
422                         /* timed out */
423                         ret = -1;
424                         goto done;
425                 }
426         } while (timeout != 0);
427
428 done:
429         if (timeout > 0) {
430                 alarm(0);
431                 CatchSignal(SIGALRM, SIG_IGN);
432         }
433
434         if (ret > 0) {
435                 DEBUG(1, ("Failed to release lock (%d, %s)!\n",
436                           ret, strerror(ret)));
437         }
438         return ret;
439 }
440
441 /* returns:
442  * negative errno on error
443  * -2 if server commands to terminate
444  * 0 if all ok
445  * ERRNO on other errors
446  */
447
448 int prefork_wait_for_client(struct pf_worker_data *pf,
449                             int lock_fd, int listen_fd,
450                             struct sockaddr *addr,
451                             socklen_t *addrlen, int *fd)
452 {
453         int ret;
454         int sd = -1;
455         int err;
456
457         ret = prefork_grab_lock(pf, lock_fd, -1);
458         if (ret != 0) {
459                 return ret;
460         }
461
462         err = 0;
463         do {
464                 sd = accept(listen_fd, addr, addrlen);
465
466                 if (sd != -1) break;
467
468                 if (errno == EINTR) {
469                         if (pf->cmds == PF_SRV_MSG_EXIT) {
470                                 err = -2;
471                         }
472                 } else {
473                         err = errno;
474                 }
475
476         } while ((sd == -1) && (err == 0));
477
478         /* return lock now, even if the accept failed.
479          * if it takes more than 10 seconds we are in deep trouble */
480         ret = prefork_release_lock(pf, lock_fd, 2);
481         if (ret != 0) {
482                 /* we were unable to release the lock!! */
483                 DEBUG(0, ("Terminating due to fatal failure!\n"));
484
485                 /* Just exit we cannot hold the whole server, better to error
486                  * on this one client and hope it was a transiet problem */
487                 err = -2;
488         }
489
490         if (err != 0) {
491                 if (sd != -1) {
492                         close(sd);
493                         sd = -1;
494                 }
495                 return err;
496         }
497
498         pf->status = PF_WORKER_BUSY;
499         pf->num_clients++;
500         *fd = sd;
501         return 0;
502 }