CUBRID Engine  latest
log_applier_sql_log.c
Go to the documentation of this file.
1 /*
2  * Copyright 2008 Search Solution Corporation
3  * Copyright 2016 CUBRID Corporation
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */
18 
19 /*
20  * log_applier_sql_log.c : SQL logging module for log applier
21  */
22 
23 #ident "$Id$"
24 
25 #include <stdio.h>
26 #include <assert.h>
27 #include <time.h>
28 #include <libgen.h>
29 #include <errno.h>
30 #include <unistd.h>
31 #include "log_applier_sql_log.h"
32 #include "system_parameter.h"
33 #include "object_primitive.h"
34 #include "object_template.h"
35 #include "object_print.h"
36 #include "error_manager.h"
37 #include "parser.h"
38 #include "work_space.h"
39 #include "class_object.h"
40 #include "environment_variable.h"
41 #include "set_object.h"
42 #include "cci_applier.h"
43 #include "schema_manager.h"
44 #include "dbtype.h"
45 
46 #include "db_value_printer.hpp"
47 #include "mem_block.hpp"
48 #include "string_buffer.hpp"
49 
50 #define SL_LOG_FILE_MAX_SIZE \
51  (prm_get_integer_value (PRM_ID_HA_SQL_LOG_MAX_SIZE_IN_MB) * 1024 * 1024)
52 #define FILE_ID_FORMAT "%d"
53 #define SQL_ID_FORMAT "%010u"
54 #define CATALOG_FORMAT FILE_ID_FORMAT " | " SQL_ID_FORMAT
55 
56 typedef struct sl_info SL_INFO;
57 struct sl_info
58 {
60  unsigned int last_inserted_sql_id;
61 };
62 
64 
65 static FILE *log_fp;
66 static FILE *catalog_fp;
67 static char sql_log_base_path[PATH_MAX];
68 static char sql_catalog_path[PATH_MAX];
69 
70 static int sl_write_sql (string_buffer & query, string_buffer * select);
71 static void sl_print_insert_att_names (string_buffer & strbuf, OBJ_TEMPASSIGN ** assignments, int num_assignments);
72 static void sl_print_insert_att_values (string_buffer & strbuf, OBJ_TEMPASSIGN ** assignments, int num_assignments);
73 static int sl_print_pk (string_buffer & strbuf, SM_CLASS * sm_class, DB_VALUE * key);
74 static void sl_print_midxkey (string_buffer & strbuf, SM_ATTRIBUTE ** attributes, const DB_MIDXKEY * midxkey);
75 static void sl_print_update_att_set (string_buffer & strbuf, OBJ_TEMPASSIGN ** assignments, int num_assignments);
76 static void sl_print_att_value (string_buffer & strbuf, const char *att_name, OBJ_TEMPASSIGN ** assignments,
77  int num_assignments);
78 static DB_VALUE *sl_find_att_value (const char *att_name, OBJ_TEMPASSIGN ** assignments, int num_assignments);
79 
80 static FILE *sl_open_next_file (FILE * old_fp);
81 static FILE *sl_log_open (void);
82 static int sl_read_catalog (void);
83 static int sl_write_catalog (void);
84 static int create_dir (const char *new_dir);
85 
86 static char *
87 trim_single_quote (char *str, size_t len)
88 {
89  if (len < 2 || str[0] != '\'' || str[len - 1] != '\'')
90  {
91  return str;
92  }
93  str[len - 1] = '\0';
94  return str + 1;
95 }
96 
97 static int
99 {
100  strbuf ("SELECT * FROM [%s] WHERE ", sm_ch_name ((MOBJ) sm_class));
101 
102  if (sl_print_pk (strbuf, sm_class, key) != NO_ERROR)
103  {
104  return ER_FAILED;
105  }
106 
107  strbuf (";");
108 
109  return NO_ERROR;
110 }
111 
112 static int
114 {
115  if (catalog_fp == NULL)
116  {
117  if ((catalog_fp = fopen (sql_catalog_path, "r+")) == NULL)
118  {
119  catalog_fp = fopen (sql_catalog_path, "w");
120  }
121  }
122 
123  if (catalog_fp == NULL)
124  {
125  er_log_debug (ARG_FILE_LINE, "Cannot open SQL catalog file: %s", strerror (errno));
126  return ER_FAILED;
127  }
128 
129  fseek (catalog_fp, 0, SEEK_SET);
130  fprintf (catalog_fp, CATALOG_FORMAT, sl_Info.curr_file_id, sl_Info.last_inserted_sql_id);
131 
132  fflush (catalog_fp);
133  fsync (fileno (catalog_fp));
134 
135  return NO_ERROR;
136 }
137 
138 static int
140 {
141  FILE *read_catalog_fp;
142  char info[LINE_MAX];
143 
144  read_catalog_fp = fopen (sql_catalog_path, "r");
145 
146  if (read_catalog_fp == NULL)
147  {
148  return sl_write_catalog ();
149  }
150 
151  if (fgets (info, LINE_MAX, read_catalog_fp) == NULL)
152  {
153  if (read_catalog_fp != NULL)
154  {
155  fclose (read_catalog_fp);
156  }
157  return ER_FAILED;
158  }
159 
160  if (sscanf (info, CATALOG_FORMAT, &sl_Info.curr_file_id, &sl_Info.last_inserted_sql_id) != 2)
161  {
162  fclose (read_catalog_fp);
163  return ER_FAILED;
164  }
165 
166  fclose (read_catalog_fp);
167  return NO_ERROR;
168 }
169 
170 int
171 sl_init (const char *db_name, const char *repl_log_path)
172 {
173  char tmp_log_path[PATH_MAX];
174  char basename_buf[PATH_MAX];
175 
176  memset (&sl_Info, 0, sizeof (sl_Info));
177 
178  snprintf (tmp_log_path, PATH_MAX, "%s/sql_log/", repl_log_path);
179  create_dir (tmp_log_path);
180 
181  strcpy (basename_buf, repl_log_path);
182  snprintf (sql_log_base_path, PATH_MAX, "%s/sql_log/%s.sql.log", repl_log_path, basename (basename_buf));
183  snprintf (sql_catalog_path, PATH_MAX, "%s/%s_applylogdb.sql.info", repl_log_path, db_name);
184 
185  sl_Info.curr_file_id = 0;
186  sl_Info.last_inserted_sql_id = 0;
187 
188  if (log_fp != NULL)
189  {
190  fclose (log_fp);
191  log_fp = NULL;
192  }
193 
194  if (catalog_fp != NULL)
195  {
196  fclose (catalog_fp);
197  catalog_fp = NULL;
198  }
199 
200  if (sl_read_catalog () != NO_ERROR)
201  {
202  return ER_FAILED;
203  }
204 
205  return NO_ERROR;
206 }
207 
208 static int
210 {
211  DB_MIDXKEY *midxkey;
212  SM_ATTRIBUTE *pk_att;
214 
215  if (pk_cons == NULL || pk_cons->attributes == NULL || pk_cons->attributes[0] == NULL)
216  {
217  return ER_FAILED;
218  }
219 
220  if (DB_VALUE_TYPE (key) == DB_TYPE_MIDXKEY)
221  {
222  midxkey = db_get_midxkey (key);
223  sl_print_midxkey (strbuf, pk_cons->attributes, midxkey);
224  }
225  else
226  {
227  pk_att = pk_cons->attributes[0];
228  strbuf ("\"%s\"=", pk_att->header.name);
229  db_value_printer printer (strbuf);
230  printer.describe_value (key);
231  }
232 
233  return NO_ERROR;
234 }
235 
236 static void
237 sl_print_insert_att_names (string_buffer & strbuf, OBJ_TEMPASSIGN ** assignments, int num_assignments)
238 {
239  if (num_assignments > 0)
240  {
241  strbuf ("\"%s\"", assignments[0]->att->header.name);
242  }
243  for (int i = 1; i < num_assignments; i++)
244  {
245  strbuf (", \"%s\"", assignments[i]->att->header.name);
246  }
247 }
248 
249 static void
250 sl_print_insert_att_values (string_buffer & strbuf, OBJ_TEMPASSIGN ** assignments, int num_assignments)
251 {
252  db_value_printer printer (strbuf);
253 
254  if (num_assignments > 0)
255  {
256  printer.describe_value (assignments[0]->variable);
257  }
258 
259  for (int i = 1; i < num_assignments; i++)
260  {
261  strbuf += ',';
262  printer.describe_value (assignments[i]->variable);
263  }
264 }
265 
266 /*
267  * sl_print_sql_midxkey - print midxkey in the following format.
268  * key1=value1 AND key2=value2 AND ...
269  */
270 static void
271 sl_print_midxkey (string_buffer & strbuf, SM_ATTRIBUTE ** attributes, const DB_MIDXKEY * midxkey)
272 {
273  int prev_i_index = 0;
274  char *prev_i_ptr = NULL;
275  DB_VALUE value;
276 
277  for (int i = 0; i < midxkey->ncolumns && attributes[i] != NULL; i++)
278  {
279  if (i > 0)
280  {
281  strbuf (" AND ");
282  }
283 
284  pr_midxkey_get_element_nocopy (midxkey, i, &value, &prev_i_index, &prev_i_ptr);
285  strbuf ("\"%s\"=", attributes[i]->header.name);
286  db_value_printer printer (strbuf);
287 
288  printer.describe_value (&value);
289  }
290 }
291 
292 static DB_VALUE *
293 sl_find_att_value (const char *att_name, OBJ_TEMPASSIGN ** assignments, int num_assignments)
294 {
295  for (int i = 0; i < num_assignments; i++)
296  {
297  if (!strcmp (att_name, assignments[i]->att->header.name))
298  {
299  return assignments[i]->variable;
300  }
301  }
302 
303  return NULL;
304 }
305 
306 static void
307 sl_print_att_value (string_buffer & strbuf, const char *att_name, OBJ_TEMPASSIGN ** assignments, int num_assignments)
308 {
309  DB_VALUE *val = sl_find_att_value (att_name, assignments, num_assignments);
310 
311  if (val != NULL)
312  {
313  db_value_printer printer (strbuf);
314  printer.describe_value (val);
315  }
316 }
317 
318 static void
319 sl_print_update_att_set (string_buffer & strbuf, OBJ_TEMPASSIGN ** assignments, int num_assignments)
320 {
321  db_value_printer printer (strbuf);
322 
323  for (int i = 0; i < num_assignments; i++)
324  {
325  strbuf ("\"%s\"=", assignments[i]->att->header.name);
326  printer.describe_value (assignments[i]->variable);
327  if (i != num_assignments - 1)
328  {
329  strbuf (", ");
330  }
331  }
332 }
333 
334 int
336 {
337  string_buffer insert_strbuf;
338 
339  insert_strbuf ("INSERT INTO [%s](", sm_ch_name ((MOBJ) (inst_tp->class_)));
340  sl_print_insert_att_names (insert_strbuf, inst_tp->assignments, inst_tp->nassigns);
341  insert_strbuf (") VALUES (");
342  sl_print_insert_att_values (insert_strbuf, inst_tp->assignments, inst_tp->nassigns);
343  insert_strbuf (");");
344 
345  string_buffer select_strbuf;
346 
347  if (sl_print_select (select_strbuf, inst_tp->class_, key) != NO_ERROR)
348  {
349  return ER_FAILED;
350  }
351 
352  if (sl_write_sql (insert_strbuf, &select_strbuf) != NO_ERROR)
353  {
354  return ER_FAILED;
355  }
356 
357  return NO_ERROR;
358 }
359 
360 int
362 {
363  int result;
364 
365  if (strcmp (sm_ch_name ((MOBJ) (inst_tp->class_)), "db_serial") != 0)
366  {
367  /* ordinary tables */
368  string_buffer update_strbuf;
369 
370  update_strbuf ("UPDATE [%s] SET ", sm_ch_name ((MOBJ) (inst_tp->class_)));
371  sl_print_update_att_set (update_strbuf, inst_tp->assignments, inst_tp->nassigns);
372  update_strbuf (" WHERE ");
373  if (sl_print_pk (update_strbuf, inst_tp->class_, key) != NO_ERROR)
374  {
375  return ER_FAILED;
376  }
377  update_strbuf (";");
378 
379  string_buffer select_strbuf;
380 
381  if (sl_print_select (select_strbuf, inst_tp->class_, key) != NO_ERROR)
382  {
383  return ER_FAILED;
384  }
385 
386  return sl_write_sql (update_strbuf, &select_strbuf);
387  }
388  else
389  {
390  /* db_serial */
391  DB_VALUE *cur_value = sl_find_att_value ("current_val", inst_tp->assignments, inst_tp->nassigns);
392  DB_VALUE *incr_value = sl_find_att_value ("increment_val", inst_tp->assignments, inst_tp->nassigns);
393 
394  if (cur_value == NULL || incr_value == NULL)
395  {
396  return ER_FAILED;
397  }
398 
399  DB_VALUE next_value;
400 
401  result = numeric_db_value_add (cur_value, incr_value, &next_value);
402  if (result != NO_ERROR)
403  {
404  return ER_FAILED;
405  }
406 
407  string_buffer serial_name_strbuf;
408 
409  sl_print_att_value (serial_name_strbuf, "name", inst_tp->assignments, inst_tp->nassigns);
410  char *serial_name = trim_single_quote ((char *) serial_name_strbuf.get_buffer (), serial_name_strbuf.len ());
411 
412  string_buffer alter_strbuf;
413  char str_next_value[NUMERIC_MAX_STRING_SIZE];
414 
415  alter_strbuf ("ALTER SERIAL [%s] START WITH %s;", serial_name,
416  numeric_db_value_print (&next_value, str_next_value));
417 
418  return sl_write_sql (alter_strbuf, NULL);
419  }
420 }
421 
422 int
423 sl_write_delete_sql (char *class_name, MOBJ mclass, DB_VALUE * key)
424 {
425  string_buffer delete_strbuf;
426 
427  delete_strbuf ("DELETE FROM [%s] WHERE ", class_name);
428  if (sl_print_pk (delete_strbuf, (SM_CLASS *) mclass, key) != NO_ERROR)
429  {
430  return ER_FAILED;
431  }
432  delete_strbuf (";");
433 
434  string_buffer select_strbuf;
435 
436  if (sl_print_select (select_strbuf, (SM_CLASS *) mclass, key) != NO_ERROR)
437  {
438  return ER_FAILED;
439  }
440 
441  return sl_write_sql (delete_strbuf, &select_strbuf);
442 }
443 
444 int
445 sl_write_statement_sql (char *class_name, char *db_user, int item_type, const char *stmt_text, char *ha_sys_prm)
446 {
447  int error = NO_ERROR;
448  char default_ha_prm[LINE_MAX];
449  SYSPRM_ERR rc;
450 
451  string_buffer statement_strbuf;
452  statement_strbuf ("%s;", stmt_text);
453 
454  if (ha_sys_prm != NULL)
455  {
456  rc = sysprm_make_default_values (ha_sys_prm, default_ha_prm, sizeof (default_ha_prm));
457  if (rc != PRM_ERR_NO_ERROR)
458  {
459  return sysprm_set_error (rc, ha_sys_prm);
460  }
461 
462  string_buffer setprm_strbuf;
463 
464  setprm_strbuf ("%s SET SYSTEM PARAMETERS '%s';", CA_MARK_TRAN_START, ha_sys_prm); //set param
465  if (sl_write_sql (setprm_strbuf, NULL) != NO_ERROR)
466  {
467  return ER_FAILED;
468  }
469  if (sl_write_sql (statement_strbuf, NULL) != NO_ERROR)
470  {
471  sl_write_sql (setprm_strbuf, NULL);
472  return ER_FAILED;
473  }
474 
475  setprm_strbuf.clear ();
476  setprm_strbuf ("%s SET SYSTEM PARAMETERS '%s';", CA_MARK_TRAN_END, default_ha_prm); //restore param
477  if (sl_write_sql (setprm_strbuf, NULL) != NO_ERROR)
478  {
479  return ER_FAILED;
480  }
481  }
482  else
483  {
484  if (sl_write_sql (statement_strbuf, NULL) != NO_ERROR)
485  {
486  return ER_FAILED;
487  }
488  }
489 
490  if (item_type == CUBRID_STMT_CREATE_CLASS)
491  {
492  if (db_user != NULL && strlen (db_user) > 0)
493  {
494  statement_strbuf.clear ();
495  statement_strbuf ("GRANT ALL PRIVILEGES ON %s TO %s;", class_name, db_user);
496  if (sl_write_sql (statement_strbuf, NULL) != NO_ERROR)
497  {
498  return ER_FAILED;
499  }
500  }
501  }
502 
503  return NO_ERROR;
504 }
505 
506 static int
508 {
509  time_t curr_time;
510  char time_buf[20];
511 
512  assert (query.get_buffer () != NULL);
513 
514  if (log_fp == NULL)
515  {
516  if ((log_fp = sl_log_open ()) == NULL)
517  {
518  return ER_FAILED;
519  }
520  }
521 
522  curr_time = time (NULL);
523  strftime (time_buf, sizeof (time_buf), "%Y-%m-%d %H:%M:%S", localtime (&curr_time));
524 
525  /* -- datetime | sql_id | is_ddl | select length | query length */
526  fprintf (log_fp, "-- %s | %u | %zu | %zu\n", time_buf, ++sl_Info.last_inserted_sql_id,
527  (select == NULL) ? 0 : select->len (), query.len ());
528 
529  /* print select for verifying data consistency */
530  if (select != NULL)
531  {
532  /* -- select_length select * from tbl_name */
533  fprintf (log_fp, "-- ");
534  fwrite (select->get_buffer (), sizeof (char), select->len (), log_fp);
535  fputc ('\n', log_fp);
536  }
537 
538  /* print SQL query */
539  fwrite (query.get_buffer (), sizeof (char), query.len (), log_fp);
540  fputc ('\n', log_fp);
541 
542  fflush (log_fp);
543 
544  sl_write_catalog ();
545 
546  fseek (log_fp, 0, SEEK_END);
547  if (ftell (log_fp) >= SL_LOG_FILE_MAX_SIZE)
548  {
550  }
551 
552  return NO_ERROR;
553 }
554 
555 static FILE *
557 {
558  char cur_sql_log_path[PATH_MAX];
559  FILE *fp;
560 
561  if (snprintf (cur_sql_log_path, PATH_MAX - 1, "%s.%d", sql_log_base_path, sl_Info.curr_file_id) < 0)
562  {
563  assert (false);
564  return NULL;
565  }
566 
567  fp = fopen (cur_sql_log_path, "r+");
568  if (fp != NULL)
569  {
570  fseek (fp, 0, SEEK_END);
571  if (ftell (fp) >= SL_LOG_FILE_MAX_SIZE)
572  {
573  fp = sl_open_next_file (fp);
574  }
575  }
576  else
577  {
578  fp = fopen (cur_sql_log_path, "w");
579  }
580 
581  if (fp == NULL)
582  {
583  er_log_debug (ARG_FILE_LINE, "Failed to open SQL log file (%s): %s", cur_sql_log_path, strerror (errno));
584  }
585 
586  return fp;
587 }
588 
589 static FILE *
590 sl_open_next_file (FILE * old_fp)
591 {
592  FILE *new_fp;
593  char new_file_path[PATH_MAX];
594 
595  sl_Info.curr_file_id++;
596  sl_Info.last_inserted_sql_id = 0;
597 
598  if (snprintf (new_file_path, PATH_MAX - 1, "%s.%d", sql_log_base_path, sl_Info.curr_file_id) < 0)
599  {
600  assert (false);
601  return NULL;
602  }
603 
604  fclose (old_fp);
605  new_fp = fopen (new_file_path, "w");
606 
607  if (sl_write_catalog () != NO_ERROR)
608  {
609  fclose (new_fp);
610  return NULL;
611  }
612 
613  return new_fp;
614 }
615 
616 static int
617 create_dir (const char *new_dir)
618 {
619  char *p, path[PATH_MAX];
620 
621  if (new_dir == NULL)
622  {
623  return ER_FAILED;
624  }
625 
626  strcpy (path, new_dir);
627 
628  p = path;
629  if (path[0] == '/')
630  {
631  p = path + 1;
632  }
633 
634  while (p != NULL)
635  {
636  p = strchr (p, '/');
637  if (p != NULL)
638  {
639  *p = '\0';
640  }
641 
642  if (access (path, F_OK) < 0)
643  {
644  if (mkdir (path, 0777) < 0)
645  {
646  return ER_FAILED;
647  }
648  }
649  if (p != NULL)
650  {
651  *p = '/';
652  p++;
653  }
654  }
655  return NO_ERROR;
656 }
static void sl_print_insert_att_values(string_buffer &strbuf, OBJ_TEMPASSIGN **assignments, int num_assignments)
#define NO_ERROR
Definition: error_code.h:46
static int sl_print_pk(string_buffer &strbuf, SM_CLASS *sm_class, DB_VALUE *key)
DB_MIDXKEY * db_get_midxkey(const DB_VALUE *value)
static int sl_write_sql(string_buffer &query, string_buffer *select)
static FILE * log_fp
int sl_init(const char *db_name, const char *repl_log_path)
char * MOBJ
Definition: work_space.h:174
#define ER_FAILED
Definition: error_code.h:47
SM_CLASS_CONSTRAINT * classobj_find_class_primary_key(SM_CLASS *class_)
static int create_dir(const char *new_dir)
int sysprm_set_error(SYSPRM_ERR rc, const char *data)
int sl_write_update_sql(DB_OTMPL *inst_tp, DB_VALUE *key)
OBJ_TEMPASSIGN ** assignments
static FILE * sl_open_next_file(FILE *old_fp)
static int sl_write_catalog(void)
#define er_log_debug(...)
static void sl_print_att_value(string_buffer &strbuf, const char *att_name, OBJ_TEMPASSIGN **assignments, int num_assignments)
static void sl_print_update_att_set(string_buffer &strbuf, OBJ_TEMPASSIGN **assignments, int num_assignments)
int sl_write_statement_sql(char *class_name, char *db_user, int item_type, const char *stmt_text, char *ha_sys_prm)
#define SL_LOG_FILE_MAX_SIZE
void describe_value(const db_value *value)
static DB_VALUE * sl_find_att_value(const char *att_name, OBJ_TEMPASSIGN **assignments, int num_assignments)
const char * sm_ch_name(const MOBJ clobj)
#define assert(x)
int sl_write_insert_sql(DB_OTMPL *inst_tp, DB_VALUE *key)
static int sl_print_select(string_buffer &strbuf, SM_CLASS *sm_class, DB_VALUE *key)
#define NULL
Definition: freelistheap.h:34
#define NUMERIC_MAX_STRING_SIZE
int sl_write_delete_sql(char *class_name, MOBJ mclass, DB_VALUE *key)
SL_INFO sl_Info
const char * get_buffer() const
int pr_midxkey_get_element_nocopy(const DB_MIDXKEY *midxkey, int index, DB_VALUE *value, int *prev_indexp, char **prev_ptrp)
#define CATALOG_FORMAT
char * db_name
static char sql_log_base_path[PATH_MAX]
static char sql_catalog_path[PATH_MAX]
size_t len() const
SYSPRM_ERR sysprm_make_default_values(const char *data, char *default_val_buf, const int buf_size)
int ncolumns
Definition: dbtype_def.h:864
static void error(const char *msg)
Definition: gencat.c:331
static int rc
Definition: serial.c:50
char * numeric_db_value_print(const DB_VALUE *val, char *buf)
unsigned int last_inserted_sql_id
#define ARG_FILE_LINE
Definition: error_manager.h:44
DB_VALUE * variable
static FILE * sl_log_open(void)
static void sl_print_insert_att_names(string_buffer &strbuf, OBJ_TEMPASSIGN **assignments, int num_assignments)
static void sl_print_midxkey(string_buffer &strbuf, SM_ATTRIBUTE **attributes, const DB_MIDXKEY *midxkey)
#define strlen(s1)
Definition: intl_support.c:43
SM_COMPONENT header
Definition: class_object.h:441
SM_ATTRIBUTE ** attributes
Definition: class_object.h:533
char * basename(const char *path)
Definition: porting.c:1132
SYSPRM_ERR
#define DB_VALUE_TYPE(value)
Definition: dbtype.h:72
int i
Definition: dynamic_load.c:954
const char * name
Definition: class_object.h:385
int numeric_db_value_add(const DB_VALUE *dbv1, const DB_VALUE *dbv2, DB_VALUE *answer)
SM_CLASS * class_
static char * trim_single_quote(char *str, size_t len)
static FILE * catalog_fp
static int sl_read_catalog(void)
const char ** p
Definition: dynamic_load.c:945