Coverage Report

Created: 2025-10-13 06:08

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