]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/jcr.c
Move more bnet functions into the BSOCK class.
[bacula/bacula] / bacula / src / lib / jcr.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2007 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation plus additions
11    that are listed in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of John Walker.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  * Manipulation routines for Job Control Records and
30  *  handling of last_jobs_list.
31  *
32  *  Kern E. Sibbald, December 2000
33  *
34  *  Version $Id$
35  *
36  *  These routines are thread safe.
37  *
38  *  The job list routines were re-written in May 2005 to
39  *  eliminate the global lock while traversing the list, and
40  *  to use the dlist subroutines.  The locking is now done
41  *  on the list each time the list is modified or traversed.
42  *  That is it is "micro-locked" rather than globally locked.
43  *  The result is that there is one lock/unlock for each entry
44  *  in the list while traversing it rather than a single lock
45  *  at the beginning of a traversal and one at the end.  This
46  *  incurs slightly more overhead, but effectively eliminates 
47  *  the possibilty of race conditions.  In addition, with the
48  *  exception of the global locking of the list during the
49  *  re-reading of the config file, no recursion is needed.
50  *
51  */
52
53 #include "bacula.h"
54 #include "jcr.h"
55
56 /* External variables we reference */
57 extern time_t watchdog_time;
58
59 /* External referenced functions */
60 void free_bregexps(alist *bregexps);
61
62 /* Forward referenced functions */
63 extern "C" void timeout_handler(int sig);
64 static void jcr_timeout_check(watchdog_t *self);
65 #ifdef TRACE_JCR_CHAIN
66 static void b_lock_jcr_chain(const char *filen, int line);
67 static void b_unlock_jcr_chain(const char *filen, int line);
68 #define lock_jcr_chain() b_lock_jcr_chain(__FILE__, __LINE__);
69 #define unlock_jcr_chain() b_unlock_jcr_chain(__FILE__, __LINE__);
70 #else
71 static void lock_jcr_chain();
72 static void unlock_jcr_chain();
73 #endif
74
75
76 int num_jobs_run;
77 dlist *last_jobs = NULL;
78 const int max_last_jobs = 10;
79  
80 static dlist *jcrs = NULL;            /* JCR chain */
81 static pthread_mutex_t jcr_lock = PTHREAD_MUTEX_INITIALIZER;
82
83 static pthread_mutex_t job_start_mutex = PTHREAD_MUTEX_INITIALIZER;
84
85 static pthread_mutex_t last_jobs_mutex = PTHREAD_MUTEX_INITIALIZER;
86
87
88 void lock_jobs()
89 {
90    P(job_start_mutex);
91 }
92
93 void unlock_jobs()
94 {
95    V(job_start_mutex);
96 }
97
98 void init_last_jobs_list()
99 {
100    JCR *jcr = NULL;
101    struct s_last_job *job_entry = NULL;
102    if (!last_jobs) {
103       last_jobs = New(dlist(job_entry, &job_entry->link));
104    }
105    if (!jcrs) {
106       jcrs = New(dlist(jcr, &jcr->link));
107    }
108 }
109
110 void term_last_jobs_list()
111 {
112    if (last_jobs) {
113       while (!last_jobs->empty()) {
114          void *je = last_jobs->first();
115          last_jobs->remove(je);
116          free(je);
117       }
118       delete last_jobs;
119       last_jobs = NULL;
120    }
121    if (jcrs) {
122       delete jcrs;
123       jcrs = NULL;
124    }
125 }
126
127 bool read_last_jobs_list(int fd, uint64_t addr)
128 {
129    struct s_last_job *je, job;
130    uint32_t num;
131
132    Dmsg1(100, "read_last_jobs seek to %d\n", (int)addr);
133    if (addr == 0 || lseek(fd, (off_t)addr, SEEK_SET) < 0) {
134       return false;
135    }
136    if (read(fd, &num, sizeof(num)) != sizeof(num)) {
137       return false;
138    }
139    Dmsg1(100, "Read num_items=%d\n", num);
140    if (num > 4 * max_last_jobs) {  /* sanity check */
141       return false;
142    }
143    for ( ; num; num--) {
144       if (read(fd, &job, sizeof(job)) != sizeof(job)) {
145          berrno be;
146          Pmsg1(000, "Read job entry. ERR=%s\n", be.bstrerror());
147          return false;
148       }
149       if (job.JobId > 0) {
150          je = (struct s_last_job *)malloc(sizeof(struct s_last_job));
151          memcpy((char *)je, (char *)&job, sizeof(job));
152          if (!last_jobs) {
153             init_last_jobs_list();
154          }
155          last_jobs->append(je);
156          if (last_jobs->size() > max_last_jobs) {
157             je = (struct s_last_job *)last_jobs->first();
158             last_jobs->remove(je);
159             free(je);
160          }
161       }
162    }
163    return true;
164 }
165
166 uint64_t write_last_jobs_list(int fd, uint64_t addr)
167 {
168    struct s_last_job *je;
169    uint32_t num;
170
171    Dmsg1(100, "write_last_jobs seek to %d\n", (int)addr);
172    if (lseek(fd, (off_t)addr, SEEK_SET) < 0) {
173       return 0;
174    }
175    if (last_jobs) {
176       /* First record is number of entires */
177       num = last_jobs->size();
178       if (write(fd, &num, sizeof(num)) != sizeof(num)) {
179          berrno be;
180          Pmsg1(000, "Error writing num_items: ERR=%s\n", be.bstrerror());
181          return 0;
182       }
183       foreach_dlist(je, last_jobs) {
184          if (write(fd, je, sizeof(struct s_last_job)) != sizeof(struct s_last_job)) {
185             berrno be;
186             Pmsg1(000, "Error writing job: ERR=%s\n", be.bstrerror());
187             return 0;
188          }
189       }
190    }
191    /* Return current address */
192    ssize_t stat = lseek(fd, 0, SEEK_CUR);
193    if (stat < 0) {
194       stat = 0;
195    }
196    return stat;
197
198 }
199
200 void lock_last_jobs_list()
201 {
202    P(last_jobs_mutex);
203 }
204
205 void unlock_last_jobs_list()
206 {
207    V(last_jobs_mutex);
208 }
209
210 /*
211  * Push a subroutine address into the job end callback stack
212  */
213 void job_end_push(JCR *jcr, void job_end_cb(JCR *jcr,void *), void *ctx)
214 {
215    jcr->job_end_push.append((void *)job_end_cb);
216    jcr->job_end_push.append(ctx);
217 }
218
219 /* Pop each job_end subroutine and call it */
220 static void job_end_pop(JCR *jcr)
221 {
222    void (*job_end_cb)(JCR *jcr, void *ctx);
223    void *ctx;
224    for (int i=jcr->job_end_push.size()-1; i > 0; ) {
225       ctx = jcr->job_end_push.get(i--);
226       job_end_cb = (void (*)(JCR *,void *))jcr->job_end_push.get(i--);
227       job_end_cb(jcr, ctx);
228    }
229 }
230
231 /*
232  * Create a Job Control Record and link it into JCR chain
233  * Returns newly allocated JCR
234  * Note, since each daemon has a different JCR, he passes
235  *  us the size.
236  */
237 JCR *new_jcr(int size, JCR_free_HANDLER *daemon_free_jcr)
238 {
239    JCR *jcr;
240    MQUEUE_ITEM *item = NULL;
241    struct sigaction sigtimer;
242
243    Dmsg0(3400, "Enter new_jcr\n");
244    jcr = (JCR *)malloc(size);
245    memset(jcr, 0, size);
246    jcr->my_thread_id = pthread_self();
247    jcr->msg_queue = New(dlist(item, &item->link));
248    jcr->job_end_push.init(1, false);
249    jcr->sched_time = time(NULL);
250    jcr->daemon_free_jcr = daemon_free_jcr;    /* plug daemon free routine */
251    jcr->init_mutex();
252    jcr->inc_use_count();   
253    jcr->VolumeName = get_pool_memory(PM_FNAME);
254    jcr->VolumeName[0] = 0;
255    jcr->errmsg = get_pool_memory(PM_MESSAGE);
256    jcr->errmsg[0] = 0;
257    /* Setup some dummy values */
258    bstrncpy(jcr->Job, "*System*", sizeof(jcr->Job));
259    jcr->JobId = 0;
260    jcr->JobType = JT_SYSTEM;          /* internal job until defined */
261    jcr->JobLevel = L_NONE;
262    set_jcr_job_status(jcr, JS_Created);       /* ready to run */
263
264    sigtimer.sa_flags = 0;
265    sigtimer.sa_handler = timeout_handler;
266    sigfillset(&sigtimer.sa_mask);
267    sigaction(TIMEOUT_SIGNAL, &sigtimer, NULL);
268
269    /*
270     * Locking jobs is a global lock that is needed
271     * so that the Director can stop new jobs from being
272     * added to the jcr chain while it processes a new
273     * conf file and does the job_end_push().
274     */
275    lock_jobs();
276    lock_jcr_chain();
277    if (!jcrs) {
278       jcrs = New(dlist(jcr, &jcr->link));
279    }
280    jcrs->append(jcr);
281    unlock_jcr_chain();
282    unlock_jobs();
283
284    return jcr;
285 }
286
287
288 /*
289  * Remove a JCR from the chain
290  * NOTE! The chain must be locked prior to calling
291  *       this routine.
292  */
293 static void remove_jcr(JCR *jcr)
294 {
295    Dmsg0(3400, "Enter remove_jcr\n");
296    if (!jcr) {
297       Emsg0(M_ABORT, 0, _("NULL jcr.\n"));
298    }
299    jcrs->remove(jcr);
300    Dmsg0(3400, "Leave remove_jcr\n");
301 }
302
303 /*
304  * Free stuff common to all JCRs.  N.B. Be careful to include only
305  *  generic stuff in the common part of the jcr.
306  */
307 static void free_common_jcr(JCR *jcr)
308 {
309    struct s_last_job *je, last_job;
310
311    /* Keep some statistics */
312    switch (jcr->JobType) {
313    case JT_BACKUP:
314    case JT_VERIFY:
315    case JT_RESTORE:
316    case JT_MIGRATE:
317    case JT_COPY:
318    case JT_ADMIN:
319       num_jobs_run++;
320       last_job.Errors = jcr->Errors;
321       last_job.JobType = jcr->JobType;
322       last_job.JobId = jcr->JobId;
323       last_job.VolSessionId = jcr->VolSessionId;
324       last_job.VolSessionTime = jcr->VolSessionTime;
325       bstrncpy(last_job.Job, jcr->Job, sizeof(last_job.Job));
326       last_job.JobFiles = jcr->JobFiles;
327       last_job.JobBytes = jcr->JobBytes;
328       last_job.JobStatus = jcr->JobStatus;
329       last_job.JobLevel = jcr->JobLevel;
330       last_job.start_time = jcr->start_time;
331       last_job.end_time = time(NULL);
332       /* Keep list of last jobs, but not Console where JobId==0 */
333       if (last_job.JobId > 0) {
334          je = (struct s_last_job *)malloc(sizeof(struct s_last_job));
335          memcpy((char *)je, (char *)&last_job, sizeof(last_job));
336          if (!last_jobs) {
337             init_last_jobs_list();
338          }
339          last_jobs->append(je);
340          if (last_jobs->size() > max_last_jobs) {
341             je = (struct s_last_job *)last_jobs->first();
342             last_jobs->remove(je);
343             free(je);
344          }
345       }
346       break;
347    default:
348       break;
349    }
350    jcr->destroy_mutex();
351
352    if (jcr->msg_queue) {
353       delete jcr->msg_queue;
354       jcr->msg_queue = NULL;
355    }
356    close_msg(jcr);                    /* close messages for this job */
357
358    /* do this after closing messages */
359    if (jcr->client_name) {
360       free_pool_memory(jcr->client_name);
361       jcr->client_name = NULL;
362    }
363
364    if (jcr->attr) {
365       free_pool_memory(jcr->attr);
366       jcr->attr = NULL;
367    }
368
369    if (jcr->sd_auth_key) {
370       free(jcr->sd_auth_key);
371       jcr->sd_auth_key = NULL;
372    }
373    if (jcr->VolumeName) {
374       free_pool_memory(jcr->VolumeName);
375       jcr->VolumeName = NULL;
376    }
377
378    if (jcr->dir_bsock) {
379       bnet_close(jcr->dir_bsock);
380       jcr->dir_bsock = NULL;
381    }
382    if (jcr->errmsg) {
383       free_pool_memory(jcr->errmsg);
384       jcr->errmsg = NULL;
385    }
386    if (jcr->where) {
387       free(jcr->where);
388       jcr->where = NULL;
389    }
390    if (jcr->RegexWhere) {
391       free(jcr->RegexWhere);
392       jcr->RegexWhere = NULL;
393    }
394    if (jcr->where_bregexp) {
395       free_bregexps(jcr->where_bregexp);
396       delete jcr->where_bregexp;
397       jcr->where_bregexp = NULL;
398    }
399    if (jcr->cached_path) {
400       free_pool_memory(jcr->cached_path);
401       jcr->cached_path = NULL;
402       jcr->cached_pnl = 0;
403    }
404    free_getuser_cache();
405    free_getgroup_cache();
406    free(jcr);
407 }
408
409 /*
410  * Global routine to free a jcr
411  */
412 #ifdef DEBUG
413 void b_free_jcr(const char *file, int line, JCR *jcr)
414 {
415    Dmsg3(3400, "Enter free_jcr 0x%x from %s:%d\n", jcr, file, line);
416
417 #else
418
419 void free_jcr(JCR *jcr)
420 {
421
422    Dmsg2(3400, "Enter free_jcr 0x%x job=%d\n", jcr, jcr->JobId);
423
424 #endif
425
426    dequeue_messages(jcr);
427    lock_jcr_chain();
428    jcr->dec_use_count();              /* decrement use count */
429    if (jcr->use_count() < 0) {
430       Emsg2(M_ERROR, 0, _("JCR use_count=%d JobId=%d\n"),
431          jcr->use_count(), jcr->JobId);
432    }
433    Dmsg3(3400, "Dec free_jcr 0x%x use_count=%d jobid=%d\n", jcr, jcr->use_count(), jcr->JobId);
434    if (jcr->use_count() > 0) {          /* if in use */
435       unlock_jcr_chain();
436       Dmsg3(3400, "free_jcr 0x%x job=%d use_count=%d\n", jcr, jcr->JobId, jcr->use_count());
437       return;
438    }
439
440    remove_jcr(jcr);                   /* remove Jcr from chain */
441    unlock_jcr_chain();
442
443    job_end_pop(jcr);                  /* pop and call hooked routines */
444
445    Dmsg1(3400, "End job=%d\n", jcr->JobId);
446    if (jcr->daemon_free_jcr) {
447       jcr->daemon_free_jcr(jcr);      /* call daemon free routine */
448    }
449    free_common_jcr(jcr);
450    close_msg(NULL);                   /* flush any daemon messages */
451    garbage_collect_memory_pool();
452    Dmsg0(3400, "Exit free_jcr\n");
453 }
454
455
456 /*
457  * Given a JobId, find the JCR
458  *   Returns: jcr on success
459  *            NULL on failure
460  */
461 JCR *get_jcr_by_id(uint32_t JobId)
462 {
463    JCR *jcr;
464
465    foreach_jcr(jcr) {
466       if (jcr->JobId == JobId) {
467          jcr->inc_use_count();
468          Dmsg2(3400, "Inc get_jcr 0x%x use_count=%d\n", jcr, jcr->use_count());
469          break;
470       }
471    }
472    endeach_jcr(jcr);
473    return jcr;
474 }
475
476 /*
477  * Given a SessionId and SessionTime, find the JCR
478  *   Returns: jcr on success
479  *            NULL on failure
480  */
481 JCR *get_jcr_by_session(uint32_t SessionId, uint32_t SessionTime)
482 {
483    JCR *jcr;
484
485    foreach_jcr(jcr) {
486       if (jcr->VolSessionId == SessionId &&
487           jcr->VolSessionTime == SessionTime) {
488          jcr->inc_use_count();
489          Dmsg2(3400, "Inc get_jcr 0x%x use_count=%d\n", jcr, jcr->use_count());
490          break;
491       }
492    }
493    endeach_jcr(jcr);
494    return jcr;
495 }
496
497
498 /*
499  * Given a Job, find the JCR
500  *  compares on the number of characters in Job
501  *  thus allowing partial matches.
502  *   Returns: jcr on success
503  *            NULL on failure
504  */
505 JCR *get_jcr_by_partial_name(char *Job)
506 {
507    JCR *jcr;
508    int len;
509
510    if (!Job) {
511       return NULL;
512    }
513    len = strlen(Job);
514    foreach_jcr(jcr) {
515       if (strncmp(Job, jcr->Job, len) == 0) {
516          jcr->inc_use_count();
517          Dmsg2(3400, "Inc get_jcr 0x%x use_count=%d\n", jcr, jcr->use_count());
518          break;
519       }
520    }
521    endeach_jcr(jcr);
522    return jcr;
523 }
524
525
526 /*
527  * Given a Job, find the JCR
528  *  requires an exact match of names.
529  *   Returns: jcr on success
530  *            NULL on failure
531  */
532 JCR *get_jcr_by_full_name(char *Job)
533 {
534    JCR *jcr;
535
536    if (!Job) {
537       return NULL;
538    }
539    foreach_jcr(jcr) {
540       if (strcmp(jcr->Job, Job) == 0) {
541          jcr->inc_use_count();
542          Dmsg2(3400, "Inc get_jcr 0x%x use_count=%d\n", jcr, jcr->use_count());
543          break;
544       }
545    }
546    endeach_jcr(jcr);
547    return jcr;
548 }
549
550 void set_jcr_job_status(JCR *jcr, int JobStatus)
551 {
552    /*
553     * For a set of errors, ... keep the current status
554     *   so it isn't lost. For all others, set it.
555     */
556    switch (jcr->JobStatus) {
557    case JS_ErrorTerminated:
558    case JS_Error:
559    case JS_FatalError:
560    case JS_Differences:
561    case JS_Canceled:
562       break;
563    default:
564       jcr->JobStatus = JobStatus;
565    }
566 }
567
568 #ifdef TRACE_JCR_CHAIN
569 static int lock_count = 0;
570 #endif
571
572 /*
573  * Lock the chain
574  */
575 #ifdef TRACE_JCR_CHAIN
576 static void b_lock_jcr_chain(const char *fname, int line)
577 #else
578 static void lock_jcr_chain()
579 #endif
580 {
581 #ifdef TRACE_JCR_CHAIN
582    Dmsg3(3400, "Lock jcr chain %d from %s:%d\n", ++lock_count, fname, line);
583 #endif
584    P(jcr_lock);
585 }
586
587 /*
588  * Unlock the chain
589  */
590 #ifdef TRACE_JCR_CHAIN
591 static void b_unlock_jcr_chain(const char *fname, int line)
592 #else
593 static void unlock_jcr_chain()
594 #endif
595 {
596 #ifdef TRACE_JCR_CHAIN
597    Dmsg3(3400, "Unlock jcr chain %d from %s:%d\n", lock_count--, fname, line);
598 #endif
599    V(jcr_lock);
600 }
601
602
603 /*
604  * Start walk of jcr chain
605  * The proper way to walk the jcr chain is:
606  *    JCR *jcr;
607  *    foreach_jcr(jcr) {
608  *      ...
609  *    }
610  *    endeach_jcr(jcr);
611  *
612  *  It is possible to leave out the endeach_jcr(jcr), but
613  *   in that case, the last jcr referenced must be explicitly
614  *   released with:
615  *
616  *    free_jcr(jcr);
617  *  
618  */
619 JCR *jcr_walk_start() 
620 {
621    JCR *jcr;
622    lock_jcr_chain();
623    jcr = (JCR *)jcrs->first();
624    if (jcr) {
625       jcr->inc_use_count();
626       Dmsg3(3400, "Inc jcr_walk_start 0x%x job=%d use_count=%d\n", jcr, jcr->JobId, jcr->use_count());
627    }
628    unlock_jcr_chain();
629    return jcr;
630 }
631
632 /*
633  * Get next jcr from chain, and release current one
634  */
635 JCR *jcr_walk_next(JCR *prev_jcr)
636 {
637    JCR *jcr;
638
639    lock_jcr_chain();
640    jcr = (JCR *)jcrs->next(prev_jcr);
641    if (jcr) {
642       jcr->inc_use_count();
643       Dmsg3(3400, "Inc jcr_walk_next 0x%x job=%d use_count=%d\n", jcr, jcr->JobId, jcr->use_count());
644    }
645    unlock_jcr_chain();
646    if (prev_jcr) {
647       free_jcr(prev_jcr);
648    }
649    return jcr;
650 }
651
652 /*
653  * Release last jcr referenced
654  */
655 void jcr_walk_end(JCR *jcr)
656 {
657    if (jcr) {
658       free_jcr(jcr);
659    }
660 }
661
662
663 /*
664  * Setup to call the timeout check routine every 30 seconds
665  *  This routine will check any timers that have been enabled.
666  */
667 bool init_jcr_subsystem(void)
668 {
669    watchdog_t *wd = new_watchdog();
670
671    wd->one_shot = false;
672    wd->interval = 30;   /* FIXME: should be configurable somewhere, even
673                          if only with a #define */
674    wd->callback = jcr_timeout_check;
675
676    register_watchdog(wd);
677
678    return true;
679 }
680
681 static void jcr_timeout_check(watchdog_t *self)
682 {
683    JCR *jcr;
684    BSOCK *fd;
685    time_t timer_start;
686
687    Dmsg0(3400, "Start JCR timeout checks\n");
688
689    /* Walk through all JCRs checking if any one is
690     * blocked for more than specified max time.
691     */
692    foreach_jcr(jcr) {
693       Dmsg2(3400, "jcr_timeout_check JobId=%u jcr=0x%x\n", jcr->JobId, jcr);
694       if (jcr->JobId == 0) {
695          continue;
696       }
697       fd = jcr->store_bsock;
698       if (fd) {
699          timer_start = fd->timer_start;
700          if (timer_start && (watchdog_time - timer_start) > fd->timeout) {
701             fd->timer_start = 0;      /* turn off timer */
702             fd->m_timed_out = true;
703             Jmsg(jcr, M_ERROR, 0, _(
704 "Watchdog sending kill after %d secs to thread stalled reading Storage daemon.\n"),
705                  watchdog_time - timer_start);
706             pthread_kill(jcr->my_thread_id, TIMEOUT_SIGNAL);
707          }
708       }
709       fd = jcr->file_bsock;
710       if (fd) {
711          timer_start = fd->timer_start;
712          if (timer_start && (watchdog_time - timer_start) > fd->timeout) {
713             fd->timer_start = 0;      /* turn off timer */
714             fd->m_timed_out = true;
715             Jmsg(jcr, M_ERROR, 0, _(
716 "Watchdog sending kill after %d secs to thread stalled reading File daemon.\n"),
717                  watchdog_time - timer_start);
718             pthread_kill(jcr->my_thread_id, TIMEOUT_SIGNAL);
719          }
720       }
721       fd = jcr->dir_bsock;
722       if (fd) {
723          timer_start = fd->timer_start;
724          if (timer_start && (watchdog_time - timer_start) > fd->timeout) {
725             fd->timer_start = 0;      /* turn off timer */
726             fd->m_timed_out = true;
727             Jmsg(jcr, M_ERROR, 0, _(
728 "Watchdog sending kill after %d secs to thread stalled reading Director.\n"),
729                  watchdog_time - timer_start);
730             pthread_kill(jcr->my_thread_id, TIMEOUT_SIGNAL);
731          }
732       }
733    }
734    endeach_jcr(jcr);
735
736    Dmsg0(3400, "Finished JCR timeout checks\n");
737 }
738
739 /*
740  * Timeout signal comes here
741  */
742 extern "C" void timeout_handler(int sig)
743 {
744    return;                            /* thus interrupting the function */
745 }