]> git.neil.brown.name Git - metad.git/blob - service.c
Assorted reformating
[metad.git] / service.c
1
2 #include        "metad.h"
3 #include        "skip.h"
4 #include        <pwd.h>
5 #include        <grp.h>
6 #include        <sys/stat.h>
7 #include        <fcntl.h>
8 #include        <errno.h>
9 #include        <values.h>
10
11 void *services;
12 void *allprocs;
13
14 int count_procs(service_t sv)
15 {
16         proc_t *pp, p;
17         int cnt;
18         time_t now = time(0);
19         for (cnt=0, pp=skip_first(sv->proc_list) ; pp ; pp=skip_next(pp))
20         {
21                 p = *pp;
22                 if (p->exit_time == 0 || p->hold_time > now || p->pipefd >=0 || p->it_forked)
23                         cnt++;
24         }
25         return cnt;
26 }
27
28
29 void check_service(service_t sv)
30 {
31         /* check for exited processes and possibly run crash */
32         proc_t *pp;
33         time_t now = time(0);
34
35         for (pp=skip_first(sv->proc_list) ; pp ; ) {
36                 proc_t p;
37                 p = *pp;
38                 pp = skip_next(pp);
39                 if (p->exit_time == 0) {
40                         /* monitoring a normal child
41                          * we should have been alerted if the child exitted,
42                          * but just-in-case... try sig0
43                          */
44                         if (kill(p->pid, 0) == -1 && errno == ESRCH && !is_saved_pid(p->pid)) {
45                                 p->status = 0; /* who knows. */
46                                 p->exit_time = now;
47                         }
48                 }
49                 if (p->exit_time > 0 && p->hold_time == 0) {
50                         /* a newly exited process */
51                         if (p->is_crash) {
52                                 /* this is a crash recover prog exitting. just set the hold time */
53                                 p->hold_time = p->exit_time + sv->next_hold;
54                         }
55                         else if (WIFSIGNALED(p->status) ||
56                                  (WIFEXITED(p->status) &&
57                                   WEXITSTATUS(p->status) != 0)) {
58                                 /* exited badly */
59                                 if (p->exit_time - p->start_time < 30) {
60                                         if (sv->next_hold < MAXINT/2)
61                                                 sv->next_hold *= 2;
62                                 } else
63                                         sv->next_hold = 2;
64                                 if (0 && sv->crash_prog) {
65                                         /* FIXME */
66                                 }
67                                 else p->hold_time = p->exit_time + sv->next_hold;
68                         }
69                         else
70                         {
71                                 if (sv->pidfile) {
72                                         /* if this file is newer than start_time and contains a pid,
73                                          * record that pid as this pid and shedule regular checks
74                                          */
75                                         int fd = open(sv->pidfile, 0);
76                                         struct stat stb;
77                                         char pidline[100];
78                                         int n;
79                                         int pid;
80                                         if (fd >= 0
81                                             && fstat(fd, &stb) != -1
82                                             && stb.st_mtime >= p->start_time
83                                             && (n=read(fd, pidline, sizeof(pidline)-1))> 0
84                                             && (pidline[n]=0 , pid=atoi(pidline)) > 1
85                                             && kill(pid, 0) != -1
86                                                 ) {
87                                                 /* looks good ! */
88                                                 p->it_forked = pid;
89                                                 p->hold_time = 1;
90                                                 waituntil(now+5); /* check it in 5 seconds and periodically */
91                                         } else {
92                                                 /* give the program a few seconds to write to the file
93                                                  * e.g. xdm forks, then creates the pid file
94                                                  */
95                                                 if (p->exit_time + 10 > now) {
96                                                         p->it_forked = -1;
97                                                         waituntil(p->exit_time + 11);
98                                                 } else {
99                                                         // give up waiting
100                                                         p->it_forked = 0;
101
102                                                         /* probably exited badly if it didn't run for long
103                                                            (keep in mind that the pidfile wasn't updated) */
104                                                         if (p->exit_time - p->start_time < 30) {
105                                                                 if (sv->next_hold < MAXINT/2)
106                                                                         sv->next_hold *= 2;
107                                                         } else
108                                                                 sv->next_hold = 2;
109
110                                                         p->hold_time = p->exit_time + sv->next_hold;
111                                                 }
112                                         }
113                                         if (fd >= 0) close(fd);
114                                 } else
115                                         sv->next_hold = 2;
116
117                         }
118                 }
119                 else if (p->exit_time > 0 && p->it_forked > 0) {
120                         /* monitoring forked child
121                          * try sending sig0 to see if process still exists
122                          */
123                         if (kill(p->it_forked, 0)== -1 && errno == ESRCH) {
124                                 p->it_forked = 0;
125                                 p->exit_time = now;
126                         } else
127                                 waituntil(now+30);      /* wait 30 seconds and check again */
128                 }
129                 if (p->pipefd >= 0) {
130                         if (readyon(p->pipefd)) {
131                                 int n = read(p->pipefd, p->pipebuf+p->bufptr, sizeof(p->pipebuf)-p->bufptr-1);
132                                 if (n<= 0) {
133                                         close(p->pipefd);
134                                         p->pipefd = -1;
135                                 } else {
136                                         int i = 0;
137                                         p->bufptr += n;
138                                         while (i < p->bufptr) {
139                                                 while (i < p->bufptr && p->pipebuf[i] != '\n')
140                                                         i++;
141                                                 if (p->pipebuf[i] == '\n' ||
142                                                     i > sizeof(p->pipebuf)-2) {
143                                                         /* ship out this much */
144                                                         int j;
145                                                         p->pipebuf[i] = '\0';
146                                                         dolog(sv, p, p->pipebuf);
147                                                         for (j=i+1 ; j<p->bufptr ; j++)
148                                                                 p->pipebuf[j-i-1] = p->pipebuf[j];
149                                                         p-> bufptr -= i+1;
150                                                         i = 0;
151                                                 } else
152                                                         i++;
153                                         }
154                                 }
155                         }
156                         if (p->pipefd >= 0)
157                                 listenon(p->pipefd);
158                 }
159                 if (p->exit_time > 0 && p->hold_time < now && p->it_forked == 0
160                     && (p->pipefd == -1 || p->exit_time +30 <now)) {
161                         /* clean up */
162                         if (p->pipefd>=0) {
163                                 /* it has been 30 seconds since the parent exitted, time to
164                                  * discard the pipe...
165                                  */
166                                 close(p->pipefd);
167                                 p->pipefd = -1;
168                         }
169                         skip_delete(sv->proc_list, &p->pid);
170                         skip_delete(allprocs, &p->pid);
171                         free(p);
172                 }
173                 else if (p->exit_time > 0 && p->hold_time > 2)
174                         waituntil(p->hold_time+1);
175         }
176         (sv->class->c_check_service)(sv);
177 }
178
179 int new_proc(service_t sv, char **env)
180 {
181         /* fork, register new child
182          * in child, call sv->class->new_child for final setup before exec
183          */
184         int pid;
185         proc_t p;
186         char **envp;
187         extern char **environ;
188         time_t now;
189         int pipefd[2];
190
191         if (sv->enabled == 0)
192                 return -2;
193         if (sv->max_proc > 0 && count_procs(sv) >= sv->max_proc)
194                 return -2; /* too many already */
195
196         if (sv->class->prefork(sv))
197                 return -2;
198
199         now = time(0);
200         if (sv->watch_output) {
201                 if (pipe(pipefd)== -1)
202                         pipefd[0] = pipefd[1] = -1;
203         }
204         switch (pid = fork()) {
205         case -1:
206                 if (sv->watch_output) {
207                         close(pipefd[0]); close(pipefd[1]);
208                 }
209                 return -1; /* cannot fork */
210         case 0: /* child */
211                 errors_to(ERROR_SYSLOG, NULL);
212                 if (sv->home_dir) {
213                         if (chdir(sv->home_dir)!=0) {
214                                 error("Couldn't chdir to %s", sv->home_dir);
215                                 exit(10);
216                         }
217                 }
218                 if (sv->username) {
219                         struct passwd *pw = getpwnam(sv->username);
220                         if (pw) {
221                                 initgroups(sv->username, pw->pw_gid);
222                                 setgid(pw->pw_gid);
223                                 setuid(pw->pw_uid);
224                         } else {
225                                 error("unknown user %s", sv->username);
226                                 exit(10);
227                         }
228                 }
229                 (sv->class->new_child)(sv);
230                 if (!env)
231                         envp = environ;
232                 else {
233                         int cnt, i;
234                         cnt = 0;
235                         for (i=0 ; env[i] ; i++) cnt++;
236                         for (i=0 ; environ[i] ; i++) cnt++;
237                         envp = (char**)malloc((cnt+1) * sizeof(char*));
238                         cnt = 0;
239                         for (i=0 ; env[i] ; i++)
240                                 envp[cnt++] = env[i];
241                         for (i=0 ; environ[i] ; i++)
242                                 envp[cnt++] = environ[i];
243                         envp[cnt] = NULL;
244                 }
245                 if (sv->watch_output && pipefd[0] >= 0) {
246                         close(pipefd[0]);
247                         if (pipefd[1] != 1) close(1);
248                         if (pipefd[1] != 2) close(2);
249                         if (pipefd[1] == 0) {
250                                 pipefd[1] = dup(pipefd[1]);
251                                 close(0);
252                         }
253                         open("/dev/null", 0);
254                         dup(pipefd[1]);
255                         if (pipefd[1] > 2) {
256                                 dup(pipefd[1]);
257                                 close(pipefd[1]);
258                         }
259                 }
260                 execve(sv->program, sv->args, envp);
261                 exit(11);
262         default: /* parent */
263                 p = (proc_t)malloc(sizeof(struct proc));
264                 if (sv->watch_output) {
265                         close(pipefd[1]);
266                         p->pipefd = pipefd[0];
267                         p->bufptr = 0;
268                         if (p->pipefd >= 0) listenon(p->pipefd);
269                         fcntl(p->pipefd, F_SETFD, 1);
270                 }
271                 else
272                         p->pipefd = -1;
273                 p->pid = pid;
274                 p->service = sv;
275                 p->it_forked = 0;
276                 p->is_crash = 0;
277                 p->start_time = now;
278                 p->hold_time = 0;
279                 p->exit_time = 0;
280                 skip_insert(sv->proc_list, p);
281                 sv->start_cnt ++;
282                 skip_insert(allprocs, p);
283                 sv->class->new_parent(sv,p);
284         }
285         return pid;
286 }
287
288 void prepare_restart(void)
289 {
290         service_t *sp;
291         for (sp = skip_first(services) ; sp ; sp=skip_next(sp)) {
292                 service_t sv = *sp;
293                 proc_t *pp;
294                 for (pp=skip_first(sv->proc_list) ; pp ; pp=skip_next(pp)) {
295                         proc_t p = *pp;
296                         if (p->pipefd >= 0)
297                                 fcntl(p->pipefd, F_SETFD, 0);
298                 }
299         }
300 }
301
302 static int proc_cmp(proc_t a, proc_t b, int *kp)
303 {
304         int k;
305         if (b) k=b->pid; else k = *kp;
306         return k - a->pid;
307 }
308
309 service_t new_service(char *name, class_t class)
310 {
311         service_t sv = (service_t)malloc(sizeof(struct service));
312
313         sv->service = strdup(name);
314         sv->class = class;
315         sv->max_proc = 1;
316         sv->crash_prog = NULL;
317         sv->home_dir = NULL;
318         sv->username = NULL;
319         sv->pidfile = NULL;
320         sv->watch_output = 0;
321         sv->enabled = 1;
322         sv->start_cnt = 0;
323         sv->program = NULL;
324         sv->args = 0;
325         sv->pending = 0;
326         sv->proc_list = skip_new(proc_cmp, NULL, NULL);
327         sv->next_hold = 2;
328         (*class->init_state)(sv);
329         return sv;
330 }
331
332 int process_opt(service_t sv, char *opt)
333 {
334         /* max= crash= dir= user= enabled= */
335
336         if (strncmp(opt, "max=", 4)==0) {
337                 sv->max_proc = atoi(opt+4);
338                 return 1;
339         } else if (strncmp(opt, "crash=", 6)==0) {
340                 sv->crash_prog = strdup(opt+6);
341                 return 1;
342         } else if (strncmp(opt, "dir=", 4)==0) {
343                 sv->home_dir = strdup(opt+4);
344                 return 1;
345         } else if (strncmp(opt, "user=", 5)==0) {
346                 sv->username = strdup(opt+5);
347                 return 1;
348         } else if (strncmp(opt, "enabled=", 8)==0) {
349                 if (strcmp(opt+8, "no")==0)
350                         sv->enabled = 0;
351                 else if (strcmp(opt+8, "yes")==0)
352                         sv->enabled = 1;
353                 else return -1;
354                 return 1;
355         } else if (strncmp(opt, "pidfile=", 8)==0) {
356                 sv->pidfile = strdup(opt+8);
357                 return 1;
358         } else if (strcmp(opt, "watch_output")==0) {
359                 sv->watch_output = 1;
360                 return 1;
361         } else
362                 return 0;
363 }
364
365 static int service_cmp(service_t a, service_t b, char *k)
366 {
367         if (b) k = b->service;
368         return strcmp(a->service, k);
369 }
370
371 void service_init(void)
372 {
373         services = skip_new(service_cmp, NULL, NULL);
374         allprocs = skip_new(proc_cmp, NULL, NULL);
375 }
376
377 void free_service(service_t sv)
378 {
379         if (sv->service) free(sv->service);
380         if (sv->crash_prog) free(sv->crash_prog);
381         if (sv->classinfo) (sv->class->free_state)(sv);
382         if (sv->home_dir) free(sv->home_dir);
383         if (sv->username) free(sv->username);
384         if (sv->program) free(sv->program);
385         if (sv->pidfile) free(sv->pidfile);
386         if (sv->args) strlistfree(sv->args);
387         if (sv->proc_list) skip_free(sv->proc_list);
388
389         free(sv);
390 }
391
392 service_t find_service(char *name)
393 {
394         service_t *sp;
395         if (name == NULL) return NULL;
396         sp = skip_search(services, name);
397         if (sp)
398                 return *sp;
399         else
400                 return NULL;
401 }