1 Star 0 Fork 0

greatitman / opensips

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
cfg_pp.c 19.23 KB
一键复制 编辑 原始数据 按行查看 历史
Liviu Chircu 提交于 2021-04-29 16:56 . Function fixups: Be less verbose
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
/*
* OpenSIPS configuration file pre-processing
*
* Copyright (C) 2019 OpenSIPS Solutions
*
* This file is part of opensips, a free SIP server.
*
* opensips is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version
*
* opensips is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
*/
#define _WITH_GETLINE
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <libgen.h>
#include <sys/stat.h>
#include "config.h"
#include "globals.h"
#include "cfg_pp.h"
#include "ut.h"
extern char *finame;
extern int startline;
extern int column;
extern FILE *yyin;
extern int yyparse();
#ifdef DEBUG_PARSER
extern int yydebug;
#endif
str include_v1 = str_init("include_file");
str include_v2 = str_init("import_file");
str cfgtok_line = str_init("__OSSPP_LINE__");
str cfgtok_filebegin = str_init("__OSSPP_FILEBEGIN__");
str cfgtok_fileend = str_init("__OSSPP_FILEEND__");
static int flatten_opensips_cfg(FILE *cfg, const char *cfg_path, str *out);
static int exec_preprocessor(FILE *flat_cfg, const char *preproc_cmdline,
str *out);
static struct cfg_context *cfg_context_new_file(const char *path);
static void cfg_context_append_line(struct cfg_context *con,
char *line, int len);
int parse_opensips_cfg(const char *cfg_file, const char *preproc_cmdline,
str *ret_buffer)
{
FILE *cfg_stream;
str cfg_buf, pp_buf;
/* fill missing arguments with the default values*/
if (!cfg_file)
cfg_file = CFG_FILE;
if (strlen(cfg_file) == 1 && cfg_file[0] == '-') {
cfg_stream = stdin;
} else {
/* load config file or die */
cfg_stream = fopen(cfg_file, "r");
if (!cfg_stream) {
LM_ERR("loading config file %s: %s\n", cfg_file,
strerror(errno));
return -1;
}
}
if (flatten_opensips_cfg(cfg_stream,
cfg_stream == stdin ? "stdin" : cfg_file, &cfg_buf) < 0) {
LM_ERR("failed to resolve file imports for %s\n", cfg_file);
return -1;
}
cfg_stream = fmemopen(cfg_buf.s, cfg_buf.len, "r");
if (!cfg_stream) {
LM_ERR("failed to open file for flattened cfg buffer\n");
goto out_free;
}
if (preproc_cmdline) {
if (exec_preprocessor(cfg_stream, preproc_cmdline, &pp_buf) < 0) {
LM_ERR("failed to exec preprocessor cmd: '%s'\n", preproc_cmdline);
goto out_free;
}
free(cfg_buf.s);
cfg_buf = pp_buf;
cfg_stream = fmemopen(cfg_buf.s, cfg_buf.len, "r");
if (!cfg_stream) {
LM_ERR("failed to open file for processed cfg buffer\n");
goto out_free;
}
}
#ifdef DEBUG_PARSER
/* used for parser debugging */
yydebug = 1;
#endif
/* parse the config file, prior to this only default values
e.g. for debugging settings will be used */
yyin = cfg_stream;
cfg_errors = 0;
if (yyparse() != 0 || cfg_errors) {
LM_ERR("bad config file (%d errors)\n", cfg_errors);
fclose(cfg_stream);
goto out_free;
}
fclose(cfg_stream);
/* do we have to return the cfg buffer? */
if (ret_buffer)
*ret_buffer = cfg_buf;
else
free(cfg_buf.s);
return 0;
out_free:
free(cfg_buf.s);
return -1;
}
static int extend_cfg_buf(char **buf, int *sz, int *bytes_left, int needed)
{
if (needed < 4096)
needed = 4096;
*buf = realloc(*buf, *sz + needed);
if (!*buf) {
LM_ERR("failed to extend cfg buf to %d\n", *sz + needed);
return -1;
}
*sz += needed;
*bytes_left += needed;
return 0;
}
/* search for '(include|import)_file "filepath"' patterns */
int mk_included_file_path(char *line, int line_len, const char *current_dir,
char **out_path)
{
#define MAX_INCLUDE_FNAME 256
static char full_path[MAX_INCLUDE_FNAME];
struct stat _;
char *p = NULL, enclose = 0;
int len1, len2, fplen;
if (line_len > include_v1.len &&
!memcmp(line, include_v1.s, include_v1.len)) {
p = line + include_v1.len;
line_len -= include_v1.len;
} else if (line_len > include_v2.len &&
!memcmp(line, include_v2.s, include_v2.len)) {
p = line + include_v2.len;
line_len -= include_v2.len;
}
if (!p)
return 1;
while (line_len > 0 && isspace(*p)) {
line_len--;
p++;
}
if (line_len < 3) // "f"
return -1;
if (*p != '"' && *p != '\'')
return -1;
enclose = *p++;
line_len--;
*out_path = p;
while (line_len > 0 && *p != enclose) {
line_len--;
p++;
}
if (line_len == 0 || p - *out_path < 2) // ""_
return -1;
*p = '\0';
/* is it a relative-path import? */
if (**out_path != '/' && stat(*out_path, &_) < 0) {
LM_DBG("%s not found (%d, %s), assuming it's relative to source cfg\n",
*out_path, errno, strerror(errno));
/* this relative path is not inside the startup dir,
* so maybe it's relative to the importing file */
len1 = strlen(current_dir);
len2 = strlen(*out_path);
if (len1 + 1 + len2 + 1 > MAX_INCLUDE_FNAME) {
LM_ERR("file path too long (max %d): '%s' + '%s'\n",
MAX_INCLUDE_FNAME, current_dir, *out_path);
return -1;
}
memcpy(full_path, current_dir, len1);
fplen = len1;
/* this test can only fail when opensips runs from '/' */
if (current_dir[len1 - 1] != '/')
full_path[fplen++] = '/';
memcpy(full_path + fplen, *out_path, len2);
fplen += len2;
full_path[fplen] = '\0';
*out_path = full_path;
}
LM_DBG("preparing to include %s\n", *out_path);
return 0;
}
static struct cfg_context {
const char *path;
const char *dirname; /* useful for relative path includes */
int loc;
char **lines;
int bufsz;
struct cfg_context *next;
} *__ccon;
static struct cfg_context *cfg_context_new_file(const char *path)
{
struct cfg_context *con, *it;
char *cpy;
for (it = __ccon; it; it = it->next)
if (!strcmp(it->path, path))
return it;
con = malloc(sizeof *con);
memset(con, 0, sizeof *con);
con->path = strdup(path);
cpy = strdup(path);
con->dirname = strdup(dirname(cpy));
free(cpy);
con->lines = malloc(32 * sizeof *con->lines);
con->bufsz = 32;
add_last(con, __ccon);
return con;
}
static void cfg_context_append_line(struct cfg_context *con,
char *line, int len)
{
if (con->loc == con->bufsz) {
con->bufsz *= 2;
con->lines = realloc(con->lines, con->bufsz * sizeof *con->lines);
if (!con->lines)
return;
}
con->lines[con->loc] = malloc(len + 1);
memcpy(con->lines[con->loc], line, len);
con->lines[con->loc][len] = '\0';
con->loc++;
}
static int __flatten_opensips_cfg(FILE *cfg, const char *cfg_path,
char **flattened, int *sz, int *bytes_left, int reclev)
{
FILE *included_cfg;
ssize_t line_len;
char *line = NULL, *included_cfg_path;
unsigned long line_buf_sz = 0;
int cfg_path_len = strlen(cfg_path);
int line_counter = 1, needed, printed;
struct cfg_context *con = NULL;
if (reclev > 50) {
LM_ERR("Maximum import depth reached (50) or "
"you have an infinite include_file loop!\n");
goto out_err;
}
if (cfg_path_len >= 2048) {
LM_ERR("file path too large: %.*s...\n", 2048, cfg_path);
goto out_err;
}
con = cfg_context_new_file(cfg_path);
needed = cfgtok_filebegin.len + 1 + 1+cfg_path_len+1 + 1 + 1;
if (*bytes_left < needed) {
if (extend_cfg_buf(flattened, sz, bytes_left, needed) < 0) {
LM_ERR("oom\n");
goto out_err;
}
}
/* print "start of file" adnotation */
printed = snprintf(*flattened + *sz - *bytes_left, *bytes_left, "%.*s \"%.*s\"\n",
cfgtok_filebegin.len, cfgtok_filebegin.s, cfg_path_len, cfg_path);
*bytes_left -= printed;
for (;;) {
line_len = getline(&line, (size_t*)&line_buf_sz, cfg);
if (line_len == -1) {
if (ferror(cfg)) {
if (errno == EINTR) {
continue;
} else {
LM_ERR("failed to read from cfg file %.*s: %d (%s)\n",
cfg_path_len, cfg_path, errno, strerror(errno));
goto out_err;
}
}
if (!feof(cfg)) {
LM_ERR("unhandled read error in cfg file %.*s: %d (%s)\n",
cfg_path_len, cfg_path, errno, strerror(errno));
goto out_err;
}
line_len = strlen(line);
break;
} else if (line_len == 0) {
continue;
}
/* fix ending lines with a missing '\n' character ;) */
if (feof(cfg)) {
if (line[line_len - 1] != '\n') {
if (line_buf_sz < line_len + 1) {
line = realloc(line, line_len + 1);
line_buf_sz = line_len + 1;
}
line[line_len] = '\n';
line_len += 1;
}
}
/* finally... we have a line! print "line number" adnotation */
needed = cfgtok_line.len + 1 + 10 + 1 + 1;
if (*bytes_left < needed) {
if (extend_cfg_buf(flattened, sz, bytes_left, needed) < 0) {
LM_ERR("oom\n");
goto out_err;
}
}
printed = snprintf(*flattened + *sz - *bytes_left, *bytes_left,
"%.*s %d\n", cfgtok_line.len, cfgtok_line.s, line_counter);
line_counter++;
*bytes_left -= printed;
if (con)
cfg_context_append_line(con, line, line_len);
/* if it's an include, skip printing the line, but do print the file */
if (mk_included_file_path(line, line_len, con->dirname, &included_cfg_path) == 0) {
included_cfg = fopen(included_cfg_path, "r");
if (!included_cfg) {
LM_ERR("failed to open %s: %d (%s)\n", included_cfg_path,
errno, strerror(errno));
goto out_err;
}
included_cfg_path = strdup(included_cfg_path);
if (__flatten_opensips_cfg(included_cfg, included_cfg_path,
flattened, sz, bytes_left, reclev + 1)) {
free(included_cfg_path);
LM_ERR("failed to flatten cfg file %s\n", cfg_path);
goto out_err;
}
free(included_cfg_path);
} else {
needed = line_len + 1;
if (*bytes_left < needed) {
if (extend_cfg_buf(flattened, sz, bytes_left, needed) < 0) {
LM_ERR("oom\n");
goto out_err;
}
}
printed = snprintf(*flattened + *sz - *bytes_left, *bytes_left,
"%.*s", (int)line_len, line);
*bytes_left -= printed;
}
}
free(line);
line = NULL;
needed = cfgtok_fileend.len + 1 + 1;
if (*bytes_left < needed) {
if (extend_cfg_buf(flattened, sz, bytes_left, needed) < 0) {
LM_ERR("oom\n");
goto out_err;
}
}
/* print "end of file" adnotation */
printed = snprintf(*flattened + *sz - *bytes_left, *bytes_left, "%.*s\n",
cfgtok_fileend.len, cfgtok_fileend.s);
*bytes_left -= printed;
fclose(cfg);
return 0;
out_err:
if (line)
free(line);
fclose(cfg);
return -1;
}
/*
* - flatten any recursive includes into one big resulting file
* - adnotate each line of the final file
* - close given FILE * and return a buffer corresponding to the new file
*/
static int flatten_opensips_cfg(FILE *cfg, const char *cfg_path, str *out)
{
int sz = 0, bytes_left = 0;
char *flattened = NULL;
if (__flatten_opensips_cfg(cfg, cfg_path, &flattened, &sz, &bytes_left, 0)) {
LM_ERR("failed to flatten cfg file %s\n", cfg_path);
return -1;
}
out->s = flattened;
out->len = sz - bytes_left;
if (strlen(out->s) != out->len) {
LM_BUG("preprocessed buffer check failed (%lu vs. %d)",
(unsigned long)strlen(out->s), out->len);
LM_ERR("either this is a bug or your script contains '\\0' chars, "
"which are obviously NOT allowed!\n");
return -1;
}
return 0;
}
static char *cfg_include_stack[CFG_MAX_INCLUDE_DEPTH];
static char **cfg_include_stackp;
int cfg_push(const str *cfg_file)
{
if (!cfg_include_stackp) {
cfg_include_stackp = cfg_include_stack;
} else if (cfg_include_stackp - cfg_include_stack + 1 >=
CFG_MAX_INCLUDE_DEPTH) {
LM_ERR("max nested cfg files reached! (%d)\n", CFG_MAX_INCLUDE_DEPTH);
return -1;
} else {
cfg_include_stackp++;
}
*cfg_include_stackp = malloc(cfg_file->len + 1);
if (!*cfg_include_stackp) {
LM_ERR("oom\n");
return -1;
}
memcpy(*cfg_include_stackp, cfg_file->s, cfg_file->len);
(*cfg_include_stackp)[cfg_file->len] = '\0';
finame = *cfg_include_stackp;
startline = 1;
column = 1;
return 0;
}
int cfg_pop(void)
{
if (!cfg_include_stackp) {
LM_ERR("no more files to pop!\n");
return -1;
}
/* the file path MUST NOT be freed, as the lexer and parser work in tandem,
* so by this point, there are plenty of structures referencing it */
if (cfg_include_stackp == cfg_include_stack) {
cfg_include_stackp = NULL;
} else {
cfg_include_stackp--;
finame = *cfg_include_stackp;
column = 1;
}
return 0;
}
void _cfg_dump_context(const char *file, int line, int colstart, int colend,
int run_once)
{
static int called_before;
struct cfg_context *con;
int i, iter = 1, len;
char *p, *end, *wsbuf, *wb, *hiline;
if (!file)
return;
for (con = __ccon; con; con = con->next)
if (!strcmp(con->path, file))
break;
if (!con || !con->lines[0] || (run_once && called_before))
return;
called_before = 1;
/* 2 lines above */
if (line >= 3) {
startline = line - 2;
iter += 2;
} else {
startline = 1;
iter += line - 1;
}
for (i = startline - 1; iter > 0; i++, iter--)
LM_GEN1(L_CRIT, "%s", con->lines[i]);
/* error indicator line */
len = strlen(con->lines[i-1]);
wsbuf = malloc(len + 1);
if (!wsbuf) {
LM_ERR("oom\n");
return;
}
wb = wsbuf;
for (p = con->lines[i-1], end = p + len; p < end && is_ws(*p); p++)
*wb++ = *p;
*wb = '\0';
if (colend < colstart) {
hiline = NULL;
} else {
hiline = malloc(colend - colstart);
if (!hiline) {
LM_ERR("oom\n");
free(wsbuf);
return;
}
memset(hiline, '~', colend - colstart);
}
LM_GEN1(L_CRIT, "%s^%.*s\n", wsbuf,
colend >= colstart ? colend - colstart : 0, hiline);
free(hiline);
free(wsbuf);
/* 2 lines below */
if (line <= con->loc - 2)
iter = 2;
else
iter = line <= con->loc ? con->loc - line : 0;
for (; iter > 0; i++, iter--)
LM_GEN1(L_CRIT, "%s", con->lines[i]);
}
void cfg_dump_backtrace(void)
{
static int called_before;
char **it;
int frame = 0;
if (called_before || !cfg_include_stackp)
return;
called_before = 1;
LM_GEN1(L_CRIT, "Traceback (last included file at the bottom):\n");
for (it = cfg_include_stack; it <= cfg_include_stackp; it++)
LM_GEN1(L_CRIT, "%2d. %s\n", frame++, *it);
}
static int exec_preprocessor(FILE *flat_cfg, const char *preproc_cmdline,
str *out)
{
int parent_w[2], parent_r[2], cfgsz = 0, cfgbufsz = 0;
char chunk[1024], *cfgbuf = NULL;
ssize_t written, bytes;
size_t bytes2write;
char *p, *tok, *cmd, **argv = NULL, *pp_binary = NULL;
int argv_len = 0, flags, have_input = 0, done_writing = 0;
if (strlen(preproc_cmdline) == 0) {
LM_ERR("preprocessor command (-p) is an empty string!\n");
goto out_err;
}
if (pipe(parent_w) != 0 || pipe(parent_r) != 0) {
LM_ERR("failed to create pipe: %d (%s)\n", errno, strerror(errno));
goto out_err;
}
/* fork a data-hungry preprocessor beast! (a.k.a. some tiny sed) */
if (fork() == 0) {
close(parent_w[1]);
if (dup2(parent_w[0], STDIN_FILENO) < 0) {
LM_ERR("dup2 failed with: %d (%s)\n", errno, strerror(errno));
exit(-1);
}
close(parent_w[0]);
close(parent_r[0]);
if (dup2(parent_r[1], STDOUT_FILENO) < 0) {
LM_ERR("dup2 failed with: %d (%s)\n", errno, strerror(errno));
exit(-1);
}
close(parent_w[1]);
for (cmd = strdup(preproc_cmdline); ; cmd = NULL) {
tok = strtok(cmd, " \t\r\n");
if (!tok)
break;
if (!pp_binary)
pp_binary = tok;
argv = realloc(argv, (argv_len + 1) * sizeof *argv);
argv[argv_len++] = tok;
}
argv = realloc(argv, (argv_len + 1) * sizeof *argv);
argv[argv_len++] = NULL;
if (pp_binary) {
execvp(pp_binary, argv);
LM_ERR("failed to exec preprocessor '%s': %d (%s)\n",
preproc_cmdline, errno, strerror(errno));
} else
LM_ERR("no binary to run: '%s'\n", preproc_cmdline);
exit(-1);
}
close(parent_w[0]);
close(parent_r[1]);
flags = fcntl(parent_w[1], F_GETFL);
if (flags == -1) {
LM_ERR("fcntl GET 1 failed: %d - %s\n", errno, strerror(errno));
goto out_err_pipes;
}
if (fcntl(parent_w[1], F_SETFL, flags | O_NONBLOCK) == -1) {
LM_ERR("fcntl SET 1 failed: %d - %s\n", errno, strerror(errno));
goto out_err_pipes;
}
flags = fcntl(parent_r[0], F_GETFL);
if (flags == -1) {
LM_ERR("fcntl GET 2 failed: %d - %s\n", errno, strerror(errno));
goto out_err_pipes;
}
if (fcntl(parent_r[0], F_SETFL, flags | O_NONBLOCK) == -1) {
LM_ERR("fcntl SET 2 failed: %d - %s\n", errno, strerror(errno));
goto out_err_pipes;
}
/* communicate with the preprocessor using alternating,
* non-blocking writes and reads */
while (!done_writing) {
/* fetch bytes to write */
bytes2write = fread(chunk, 1, 1024, flat_cfg);
if (ferror(flat_cfg)) {
LM_ERR("failed to read from flat cfg: %d (%s)\n",
errno, strerror(errno));
goto out_err_pipes;
}
if (bytes2write == 0) {
done_writing = 1;
close(parent_w[1]); /* signal EOF to the outside process! */
} else {
have_input = 1;
}
p = chunk;
send_bytes:
/* write phase */
while (bytes2write > 0) {
written = write(parent_w[1], p, bytes2write);
if (written < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
else if (errno == EINTR)
continue;
else
goto out_err_pipes;
}
bytes2write -= written;
p += written;
}
/* read phase */
for (;;) {
if (cfgsz + 1024 > cfgbufsz) {
if (cfgbufsz == 0)
cfgbufsz = 4096;
else
cfgbufsz *= 2;
cfgbuf = realloc(cfgbuf, cfgbufsz);
if (!cfgbuf) {
LM_ERR("oom, failed to build config buffer\n");
goto out_err;
}
}
bytes = read(parent_r[0], cfgbuf + cfgsz, 1024);
if (bytes < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (done_writing) {
usleep(10);
continue;
} else {
break;
}
} else if (errno == EINTR) {
continue;
} else {
goto out_err_pipes;
}
} else if (bytes == 0) {
bytes2write = 0;
done_writing = 1;
break;
}
cfgsz += bytes;
}
if (bytes2write > 0)
goto send_bytes;
}
if (have_input && cfgsz == 0)
LM_WARN("no output from the preprocessor! "
"Does it print to standard output?\n");
fclose(flat_cfg);
close(parent_r[0]);
out->s = cfgbuf;
out->len = cfgsz;
return 0;
out_err_pipes:
close(parent_w[1]);
close(parent_r[0]);
out_err:
fclose(flat_cfg);
free(cfgbuf);
return -1;
}
int eatback_pp_tok(struct str_buf *buf)
{
char *p;
str last_line;
if (!buf->s)
return 0;
for (p = buf->crt - 1; p >= buf->s; p--)
if (*p == '\n') {
p++;
goto match_pp_tok;
}
return 0;
match_pp_tok:
last_line.s = p;
last_line.len = buf->crt - p;
if (last_line.len < 0) {
LM_BUG("negative line len");
return 0;
}
if (last_line.len >= cfgtok_line.len &&
!memcmp(last_line.s, cfgtok_line.s, cfgtok_line.len))
goto clear_last_line;
if (last_line.len >= cfgtok_filebegin.len &&
!memcmp(last_line.s, cfgtok_filebegin.s, cfgtok_filebegin.len))
goto clear_last_line;
if (last_line.len >= cfgtok_fileend.len &&
!memcmp(last_line.s, cfgtok_fileend.s, cfgtok_fileend.len))
goto clear_last_line;
/* don't touch anything, this is an actual script line! */
return 0;
clear_last_line:
LM_DBG("clearing pp token line: '%.*s'\n", (int)(buf->crt - p), p);
buf->left += buf->crt - p;
*p = '\0';
buf->crt = p;
return 1;
}
1
https://gitee.com/greatitman/opensips.git
git@gitee.com:greatitman/opensips.git
greatitman
opensips
opensips
master

搜索帮助

53164aa7 5694891 3bd8fe86 5694891