Coverage Report

Created: 2026-07-16 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/opensips/cfg_reload.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2019 OpenSIPS Solutions
3
 *
4
 * This file is part of opensips, a free SIP server.
5
 *
6
 * opensips 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
 * opensips 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
19
 */
20
21
22
#include <unistd.h>
23
#include <errno.h>
24
25
#include "mem/mem.h"
26
#include "globals.h"
27
#include "locking.h"
28
#include "rw_locking.h"
29
#include "daemonize.h"
30
#include "pt.h"
31
#include "route.h"
32
#include "reactor_defs.h"
33
#include "cfg_pp.h"
34
#include "cfg_reload.h"
35
36
extern FILE *yyin;
37
extern int yyparse();
38
#ifdef DEBUG_PARSER
39
extern int yydebug;
40
#endif
41
42
/* maximum number of milliseconds to wait for processes to get to final
43
 * conclusion over validating a script reloading */
44
0
#define MAX_PROC_RELOAD_WAIT 20000
45
46
enum proc_reload_status {
47
  RELOAD_NONE=0,        /* no reload going on */
48
  RELOAD_SENT,          /* reload cmd sent */
49
  RELOAD_RECEIVED,      /* reload cmd received by proc (load in progress)*/
50
  RELOAD_SUCCESS,       /* cfg reload succeded */
51
  RELOAD_FAILED         /* cfg reload failed */
52
  };
53
54
struct script_reload_ctx {
55
  gen_lock_t lock;
56
  rw_lock_t *rw_lock;
57
  unsigned int seq_no;
58
  unsigned int next_seq_no;
59
  str reloaded_cfg_buf;
60
  str cfg_buf;
61
  enum proc_reload_status *proc_status;
62
};
63
64
65
int cfg_parse_only_routes = 0;
66
67
/* scripting routes reload context */
68
struct script_reload_ctx *srr_ctx = NULL;
69
70
/* if currently we run an older version of the cfg, resulting form a recent
71
 * reload - this just a santinel variable when (due async resume) we have to
72
 * go back and use the old script (which was used for triggering the async)
73
 * Of course, this is per process */
74
int _running_old_script = 0;
75
76
/* if we still keep the old/previous cfg (as a result of a recent reload)
77
 * this will stay on as time as we have in memory the old script. When the
78
 * old script is free, this will also be reset (also see the above comment) */
79
int _have_old_script = 0;
80
81
/* old/prev cfg - we may need to keep it in paralle with the new one in 
82
 * order to properly complete the ongoing async operatation */
83
static struct os_script_routes *prev_sr=NULL;
84
85
static struct os_script_routes *swap_bk = NULL;
86
87
88
89
void reload_swap_old_script(void)
90
0
{
91
0
  swap_bk = sroutes;
92
0
  sroutes = prev_sr;
93
0
}
94
95
96
void reload_swap_current_script(void)
97
0
{
98
0
  sroutes = swap_bk;
99
0
}
100
101
102
void reload_free_old_cfg(void)
103
0
{
104
0
  LM_ERR("finally removing the old/prev cfg\n");
105
0
  free_route_lists(prev_sr);
106
0
  prev_sr = NULL;
107
0
  _have_old_script = 0;
108
0
}
109
110
111
int init_script_reload(void)
112
0
{
113
0
  srr_ctx = (struct script_reload_ctx *)shm_malloc( sizeof(*srr_ctx) +
114
0
    counted_max_processes*sizeof(enum proc_reload_status) );
115
0
  if (srr_ctx==NULL) {
116
0
    LM_ERR("failed to shm allocate the script reload context\n");
117
0
    return -1;
118
0
  }
119
120
0
  memset( srr_ctx, 0, sizeof(*srr_ctx) +
121
0
    counted_max_processes*sizeof(enum proc_reload_status));
122
123
0
  srr_ctx->next_seq_no = 1;
124
125
0
  lock_init( &srr_ctx->lock );
126
127
0
  srr_ctx->rw_lock = lock_init_rw();
128
0
  if (srr_ctx->rw_lock==NULL) {
129
0
    LM_ERR("failed to create rw lock for script reload context\n");
130
0
    shm_free(srr_ctx);
131
0
    srr_ctx = NULL;
132
0
    return -1;
133
0
  }
134
135
0
  srr_ctx->proc_status = (enum proc_reload_status*)(srr_ctx + 1);
136
137
0
  return 0;
138
0
}
139
140
141
static inline void reset_script_reload_ctx(void)
142
0
{
143
0
  if (srr_ctx->reloaded_cfg_buf.s)
144
0
    shm_free(srr_ctx->reloaded_cfg_buf.s);
145
0
  srr_ctx->reloaded_cfg_buf = srr_ctx->cfg_buf;
146
0
  srr_ctx->cfg_buf.s = NULL;
147
0
  srr_ctx->cfg_buf.len = 0;
148
149
0
  memset( srr_ctx->proc_status, 0,
150
0
    counted_max_processes*sizeof(enum proc_reload_status));
151
152
153
  /* this must be the last as it will allow the ctx reusage
154
   * for another reload */
155
0
  srr_ctx->seq_no = 0;
156
0
}
157
158
159
static inline void send_cmd_to_all_procs(ipc_rpc_f *rpc)
160
0
{
161
0
  int i;
162
163
  /* send it to all process with IPC and needing SCRIPT */
164
165
0
  for( i=1 ; i<counted_max_processes ; i++) {
166
0
    if ( (pt[i].flags&(OSS_PROC_NO_IPC|OSS_PROC_NEEDS_SCRIPT))==
167
0
    OSS_PROC_NEEDS_SCRIPT ) {
168
      /* set the status before sending, to avoid any race condition
169
       * with running the callback function */
170
0
      srr_ctx->proc_status[i] = RELOAD_SENT;
171
0
      if (i==process_no) {
172
        /* run line the cmd for the proc itself */
173
0
        rpc( process_no, (void*)(long)srr_ctx->seq_no);
174
0
      } else {
175
0
        if (ipc_send_rpc( i, rpc, (void*)(long)srr_ctx->seq_no)<0)
176
0
          srr_ctx->proc_status[i] = RELOAD_FAILED;
177
0
      }
178
0
    }
179
0
  }
180
0
}
181
182
183
static inline int check_status_of_all_procs(enum proc_reload_status min_status,
184
                      enum proc_reload_status max_status)
185
0
{
186
0
  int i;
187
188
0
  for( i=1 ; i<counted_max_processes ; i++) {
189
0
    if ( (pt[i].flags&(OSS_PROC_NO_IPC|OSS_PROC_NEEDS_SCRIPT))==
190
0
    OSS_PROC_NEEDS_SCRIPT ) {
191
0
        if (srr_ctx->proc_status[i]<min_status ||
192
0
        srr_ctx->proc_status[i]>max_status)
193
0
          return -1;
194
0
    }
195
0
  }
196
197
0
  return 1;
198
0
}
199
200
201
/* this is used only for debugging purposes */
202
static inline int list_status_of_all_procs(void)
203
0
{
204
0
  int i;
205
206
0
  for( i=1 ; i<counted_max_processes ; i++) {
207
0
    if ( (pt[i].flags&(OSS_PROC_NO_IPC|OSS_PROC_NEEDS_SCRIPT))==
208
0
    OSS_PROC_NEEDS_SCRIPT ) {
209
0
      LM_INFO("process %d [%d] reported status %d\n",
210
0
        i, pt[i].pid, srr_ctx->proc_status[i]);
211
0
    } else {
212
0
      LM_INFO("process %d [%d] not needing script \n", 
213
0
        i, pt[i].pid);
214
0
    }
215
0
  }
216
217
0
  return 1;
218
0
}
219
220
221
/* global, per process holder for a new script to be used (during reload).
222
   This is used to store the pending-to-use script during the validation
223
   step and the actual switching (to new script) step */
224
static struct os_script_routes *parsed_sr=NULL;
225
226
227
static void routes_reload_per_proc(int sender, void *param)
228
0
{
229
0
  struct os_script_routes *sr_bk;
230
0
  int seq_no = (int)(long)param;
231
0
  FILE *cfg;
232
233
0
  LM_DBG("reload cmd received in process %d, with seq no %d\n",
234
0
    process_no, seq_no);
235
236
0
  if (_have_old_script) {
237
0
    LM_ERR("cannot reload again as still having the previous cfg,"
238
0
      " retry later\n");
239
0
    srr_ctx->proc_status[process_no] = RELOAD_FAILED;
240
0
    return;
241
0
  }
242
243
0
  lock_start_read(srr_ctx->rw_lock);
244
245
0
  if (srr_ctx->seq_no==0 || srr_ctx->seq_no!=seq_no) {
246
0
    LM_INFO("dropping reload cmd due out of sequence reason\n");
247
0
    lock_stop_read(srr_ctx->rw_lock);
248
0
    return;
249
0
  }
250
251
0
  srr_ctx->proc_status[process_no] = RELOAD_RECEIVED;
252
253
  /* get a file stream from the buffer */
254
0
  cfg = fmemopen( srr_ctx->cfg_buf.s, srr_ctx->cfg_buf.len, "r");
255
0
  if (!cfg) {
256
0
    LM_ERR("failed to obtain file from cfg buffer\n");
257
0
    goto error;
258
0
  }
259
260
  /* get and set a new script routes holder (for new cfg) */
261
0
  if (parsed_sr) {
262
    /* probabaly left by mistake from a prev attempt ?? */
263
0
    free_route_lists(parsed_sr);
264
0
    pkg_free(parsed_sr);
265
0
  }
266
0
  parsed_sr = new_sroutes_holder( 1 );
267
0
  if (parsed_sr==NULL) {
268
0
    LM_ERR("failed to allocate a new script routes holder\n");
269
0
    fclose(cfg);
270
0
    goto error;
271
0
  }
272
0
  sr_bk = sroutes;
273
0
  sroutes = parsed_sr;
274
275
  /* parse, but only the routes */
276
0
  cfg_parse_only_routes = 1;
277
0
  yyin = cfg;
278
0
  if (yyparse() != 0 || cfg_errors) {
279
0
    LM_ERR("bad config file (%d errors)\n", cfg_errors);
280
0
    fclose(cfg);
281
0
    goto error;
282
0
  }
283
0
  fclose(cfg);
284
0
  cfg_parse_only_routes = 0;
285
286
0
  if (fix_rls()<0) {
287
0
    LM_ERR("fixing routes failed, abording\n");
288
0
    goto error;
289
0
  }
290
291
0
  sroutes = sr_bk;
292
293
  /* keep the parsed routes, waiting for the confirmation to switch */
294
295
0
  srr_ctx->proc_status[process_no] = RELOAD_SUCCESS;
296
297
0
  lock_stop_read(srr_ctx->rw_lock);
298
0
  LM_INFO("process successfully parsed new cfg (seq %d)\n",seq_no);
299
300
0
  return;
301
302
0
error:
303
0
  srr_ctx->proc_status[process_no] = RELOAD_FAILED;
304
0
  lock_stop_read(srr_ctx->rw_lock);
305
0
  if (parsed_sr) {
306
0
    free_route_lists(parsed_sr);
307
0
    pkg_free(parsed_sr);
308
0
    parsed_sr = NULL;
309
0
  }
310
311
0
  return;
312
0
}
313
314
315
static void routes_switch_per_proc(int sender, void *param)
316
0
{
317
0
  int seq_no = (int)(long)param;
318
319
0
  LM_DBG("swich cmd received in process %d, with seq no %d\n",
320
0
    process_no, seq_no);
321
322
0
  if (srr_ctx->seq_no!=0 && srr_ctx->seq_no!=seq_no) {
323
0
    LM_INFO("dropping switch cmd due out of sequence reason\n");
324
0
    if (parsed_sr) free_route_lists(parsed_sr);
325
0
    parsed_sr = NULL;
326
0
    return;
327
0
  }
328
329
  /* handle the async fd - mark them and see if we have any; if yes, 
330
   * then we need to keep the previous cfg until all the async are done */
331
0
  reactor_set_app_flag( F_SCRIPT_ASYNC, REACTOR_RELOAD_TAINTED_FLAG);
332
0
  reactor_set_app_flag(     F_FD_ASYNC, REACTOR_RELOAD_TAINTED_FLAG);
333
0
  reactor_set_app_flag( F_LAUNCH_ASYNC, REACTOR_RELOAD_TAINTED_FLAG);
334
335
0
  if (reactor_check_app_flag(REACTOR_RELOAD_TAINTED_FLAG)) {
336
    /* we do have onlgoing aync fds */
337
0
    LM_DBG("keeping previous cfg until all ongoing async complete\n");
338
0
    prev_sr = sroutes;
339
0
    _have_old_script = 1;
340
0
  } else {
341
    /* we can get rid of the script right away*/
342
0
    LM_DBG("no ongoing async, freeing the previous cfg\n");
343
0
    free_route_lists(sroutes);
344
0
    prev_sr = NULL;
345
0
    _have_old_script = 0;
346
0
  }
347
348
  /* swap the old route set with the new parsed set */
349
0
  sroutes = parsed_sr;
350
0
  parsed_sr = NULL;
351
352
  /* update all the ref to script routes */
353
0
  update_all_script_route_refs();
354
0
  print_script_route_refs();
355
0
}
356
357
358
int self_update_routing_script(void)
359
0
{
360
0
  int ret = 0;
361
362
  /* be sure we do not overlap with a script reload */
363
0
  lock_get( &srr_ctx->lock );
364
0
  if (srr_ctx->seq_no!=0) {
365
0
    LM_INFO("Reload already in progress, cannot update now\n");
366
0
    lock_release( &srr_ctx->lock );
367
0
    return -1;
368
0
  }
369
0
  srr_ctx->seq_no = srr_ctx->next_seq_no++;
370
0
  lock_release( &srr_ctx->lock );
371
372
  /* anything to reload? */
373
0
  if (srr_ctx->reloaded_cfg_buf.s==NULL)
374
0
    goto done;
375
376
  /* put the last reloaded buffer in the right place as for a reload */
377
0
  srr_ctx->cfg_buf = srr_ctx->reloaded_cfg_buf;
378
379
0
  routes_reload_per_proc( process_no, (void*)(long)srr_ctx->seq_no);
380
0
  if (srr_ctx->proc_status[process_no] != RELOAD_SUCCESS) {
381
0
    LM_ERR("failed to update to the last reloaded cfg :(\n");
382
0
    ret = -1;
383
0
    goto done;
384
0
  }
385
386
0
  routes_switch_per_proc( process_no, (void*)(long)srr_ctx->seq_no);
387
388
0
done:
389
0
  srr_ctx->cfg_buf.s = NULL;
390
0
  srr_ctx->cfg_buf.len = 0;
391
  /* this must be the last as it will allow the ctx reusage
392
   * for another reload */
393
0
  srr_ctx->seq_no = 0;
394
395
0
  return ret;
396
0
}
397
398
399
/* This is the trigger point for script reloading
400
 */
401
int reload_routing_script(void)
402
0
{
403
0
  struct os_script_routes *sr, *sr_bk;
404
0
  char * curr_wdir=NULL;
405
0
  str cfg_buf={NULL,0};
406
0
  int cnt_sleep, ret;
407
408
  /* one reload at a time */
409
0
  lock_get( &srr_ctx->lock );
410
0
  if (srr_ctx->seq_no!=0) {
411
0
    LM_INFO("Reload already in progress, cannot start a new one\n");
412
0
    lock_release( &srr_ctx->lock );
413
0
    return -1;
414
0
  }
415
0
  srr_ctx->seq_no = srr_ctx->next_seq_no++;
416
0
  lock_release( &srr_ctx->lock );
417
418
0
  sr = new_sroutes_holder( 0 );
419
0
  if (sr==NULL) {
420
0
    LM_ERR("failed to allocate a new script routes holder\n");
421
0
    goto error;
422
0
  }
423
424
0
  LM_INFO("reparsing routes from <%s> file\n",cfg_file);
425
426
0
  sr_bk = sroutes;
427
0
  sroutes = sr;
428
429
  /* parse, but only the routes */
430
0
  cfg_parse_only_routes = 1;
431
432
  /* switch to the startup working dir, to be sure the file pathname 
433
   * (as given at startup via cli) still match */
434
0
  if (startup_wdir) {
435
0
    if ( (curr_wdir=getcwd(NULL,0))==NULL) {
436
0
      LM_ERR("failed to determin the working dir %d/%s\n", errno,
437
0
        strerror(errno));
438
0
      goto error;
439
0
    }
440
0
    if (chdir(startup_wdir)<0){
441
0
      LM_CRIT("Cannot chdir to %s: %s\n", startup_wdir, strerror(errno));
442
0
      goto error;
443
0
    }
444
0
  }
445
446
0
  ret = parse_opensips_cfg( cfg_file, preproc, &cfg_buf);
447
448
0
  cfg_parse_only_routes = 0;
449
450
  /* revert to the original working dir */
451
0
  if (curr_wdir) {
452
0
    if (chdir(curr_wdir)<0){
453
0
      LM_CRIT("Cannot chdir to %s: %s\n", curr_wdir, strerror(errno));
454
0
    }
455
0
    free(curr_wdir);
456
0
    curr_wdir=NULL;
457
0
  }
458
459
0
  if (ret<0) {
460
0
    LM_ERR("parsing failed, abording\n");
461
0
    goto error;
462
0
  }
463
464
0
  LM_INFO("fixing the loaded routes\n");
465
466
0
  if (fix_rls()<0) {
467
0
    LM_ERR("fixing routes failed, abording\n");
468
0
    goto error;
469
0
  }
470
471
  /* trigger module's validation functions to check if the reload of this 
472
   * new route set is "approved" */
473
0
  if (!modules_validate_reload()) {
474
0
    LM_ERR("routes validation by modules failed, abording reload. "
475
0
      "OpenSIPS restart is recomended to deploy the new script\n");
476
0
    goto error;
477
0
  }
478
479
  /* we do not need the cfg, so free it and restore previous set of routes */
480
0
  sroutes = sr_bk;
481
0
  free_route_lists(sr);
482
0
  pkg_free(sr);
483
0
  sr = NULL;
484
485
0
  LM_DBG("new routes are valid and approved, push it to all procs\n");
486
487
488
0
  if (shm_nt_str_dup( &srr_ctx->cfg_buf, &cfg_buf)<0) {
489
0
    LM_ERR("failed to shmem'ize the cfg buffer, abording\n");
490
0
    goto error;
491
0
  }
492
493
  /* we do not need the local cfg buffer anymore */
494
0
  free( cfg_buf.s );
495
0
  cfg_buf.s = NULL;
496
0
  cfg_buf.len = 0;
497
498
  /* send the script for parse and validation to all procs */
499
0
  send_cmd_to_all_procs( routes_reload_per_proc );
500
501
0
  LM_DBG("reload triggered into all processes, waiting...\n");
502
503
  /* wait until all the processes validate (or not) the new cfg */
504
0
  cnt_sleep = 0;
505
0
  while ( (cnt_sleep++)<MAX_PROC_RELOAD_WAIT &&
506
0
  check_status_of_all_procs( RELOAD_SUCCESS, RELOAD_FAILED)==-1)
507
0
    usleep(1000);
508
509
0
  LM_DBG("done with waiting after %d miliseconds\n",cnt_sleep);
510
511
  /* done with waiting -> check what happened so far, but be sure all
512
   * procs are not during a reload validation (in progress) */
513
0
  lock_start_write(srr_ctx->rw_lock);
514
515
  /* no other proc is doing script validation anymore,
516
   * so recheck the status */
517
0
  if (check_status_of_all_procs( RELOAD_SUCCESS, RELOAD_SUCCESS)!=1) {
518
0
    LM_INFO("not all processes managed to load the new script, "
519
0
      "aborting the reload\n");
520
0
    list_status_of_all_procs( );
521
    /* some processes failed with the reload - setting an out-of-order
522
     * sequence number will prevent any potential process waiting to 
523
     * start the reload to actually do it */
524
0
    srr_ctx->seq_no = INT_MAX;
525
    /* if the script was succesfully loaded by some procs, it will
526
     * be freed upon next reload attempt due sequence number */
527
0
    lock_stop_write(srr_ctx->rw_lock);
528
0
    goto error;
529
0
  }
530
531
0
  LM_DBG("all procs successfully reloaded, send the switch cmd\n");
532
533
0
  send_cmd_to_all_procs( routes_switch_per_proc );
534
535
0
  register_route_timers();
536
537
  /* ready for a new reload :) */
538
0
  reset_script_reload_ctx();
539
540
0
  lock_stop_write(srr_ctx->rw_lock);
541
542
0
  return 0;
543
0
error:
544
  /* allow other reloads to be triggered */
545
0
  reset_script_reload_ctx();
546
  /* do cleanup */
547
0
  if (curr_wdir) free(curr_wdir);
548
0
  if (sr) {
549
0
    free_route_lists(sr);
550
0
    pkg_free(sr);
551
0
    sroutes = sr_bk;
552
0
  }
553
0
  if (cfg_buf.s)
554
0
    free(cfg_buf.s);
555
0
  return -1;
556
0
}
557