BRL-CAD
db_scan.c
Go to the documentation of this file.
1 /* D B _ S C A N . C
2  * BRL-CAD
3  *
4  * Copyright (c) 1994-2014 United States Government as represented by
5  * the U.S. Army Research Laboratory.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public License
9  * version 2.1 as published by the Free Software Foundation.
10  *
11  * This library is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this file; see the file named COPYING for more
18  * information.
19  */
20 /** @addtogroup db4 */
21 /** @{ */
22 /** @file librt/db_scan.c
23  *
24  * Routines to sequentially read database, send objects to handlers,
25  * process ident records, and perform unit conversion.
26  *
27  */
28 
29 #include "common.h"
30 
31 #include <stdio.h>
32 #include <string.h>
33 #include "bnetwork.h"
34 
35 #include "bu/units.h"
36 #include "vmath.h"
37 #include "db.h"
38 #include "raytrace.h"
39 #include "mater.h"
40 
41 #include "./librt_private.h"
42 
43 
44 #define DEBUG_PR(aaa, rrr) {\
45  if (RT_G_DEBUG&DEBUG_DB) bu_log("db_scan %ld %c (0%o)\n", \
46  aaa, rrr.u_id, rrr.u_id); }
47 
48 /**
49  * This routine sequentially reads through the model database file and
50  * builds a directory of the object names, to allow rapid named access
51  * to objects.
52  *
53  * Note that some multi-record database items include length fields.
54  * These length fields are not used here. Instead, the sizes of
55  * multi-record items are determined by reading ahead and computing
56  * the actual size. This prevents difficulties arising from external
57  * "adjustment" of the number of records without corresponding
58  * adjustment of the length fields. In the future, these length
59  * fields will be phased out.
60  *
61  * The handler will be called with a variety of args. The handler is
62  * responsible for handling name strings of exactly NAMESIZE chars.
63  * The most common example of such a function is db_diradd().
64  *
65  * Note that the handler may do I/O, including repositioning the file
66  * pointer, so this must be taken into account.
67  *
68  * Returns -
69  * 0 Success
70  * -1 Fatal Error
71  */
72 int
73 db_scan(struct db_i *dbip, int (*handler) (struct db_i *, const char *, off_t, size_t, int, void *), int do_old_matter, void *client_data)
74 
75 
76 /* argument for handler */
77 {
78  union record record; /* Initial record, holds name */
79  union record rec2; /* additional record(s) */
80  register off_t addr; /* start of current rec */
81  register off_t here; /* intermediate positions */
82  register off_t next; /* start of next rec */
83  register int nrec; /* # records for this solid */
84  register int totrec; /* # records for database */
85  register long j;
86 
87  RT_CK_DBI(dbip);
88  if (RT_G_DEBUG&DEBUG_DB) bu_log("db_scan(%p, %lx)\n",
89  (void *)dbip, (long unsigned int)handler);
90 
91  /* XXXX Note that this ignores dbip->dbi_inmem */
92  /* In a portable way, read the header (even if not rewound) */
93  rewind(dbip->dbi_fp);
94  if (fread((char *)&record, sizeof record, 1, dbip->dbi_fp) != 1 ||
95  record.u_id != ID_IDENT) {
96  bu_log("db_scan ERROR: File is lacking a proper MGED database header\n");
97  return -1;
98  }
99  rewind(dbip->dbi_fp);
100  next = bu_ftell(dbip->dbi_fp);
101  if (next < 0) {
102  perror("ftell");
103  next = 0;
104  }
105 
106  here = -1;
107  addr = 0;
108 
109  totrec = 0;
110  while (1) {
111  nrec = 0;
112  if (bu_fseek(dbip->dbi_fp, next, 0) != 0) {
113  bu_log("db_scan: fseek(offset=%zd) failure\n", next);
114  return -1;
115  }
116  addr = next;
117 
118  if (fread((char *)&record, sizeof record, 1, dbip->dbi_fp) != 1
119  || feof(dbip->dbi_fp))
120  break;
121  next = bu_ftell(dbip->dbi_fp);
122  if (next < 0) {
123  perror("db_scan: ftell: ");
124  return -1;
125  }
126  DEBUG_PR(addr, record);
127 
128  nrec++;
129  switch (record.u_id) {
130  case ID_IDENT:
131  if (!BU_STR_EQUAL(record.i.i_version, ID_VERSION)) {
132  bu_log("db_scan WARNING: File is Version %s, Program is version %s\n",
133  record.i.i_version, ID_VERSION);
134  }
135  /* Record first IDENT records title string */
136  if (dbip->dbi_title == (char *)0) {
137  dbip->dbi_title = bu_strdup(record.i.i_title);
138  db_conversions(dbip, record.i.i_units);
139  }
140  break;
141  case ID_FREE:
142  /* Inform db manager of avail. space */
143  rt_memfree(&(dbip->dbi_freep), 1, addr/sizeof(union record));
144  break;
145  case ID_ARS_A:
146  while (1) {
147  here = bu_ftell(dbip->dbi_fp);
148  if (fread((char *)&rec2, sizeof(rec2),
149  1, dbip->dbi_fp) != 1)
150  break;
151  DEBUG_PR(here, rec2);
152  if (rec2.u_id != ID_ARS_B) {
153  bu_fseek(dbip->dbi_fp, here, 0);
154  break;
155  }
156  nrec++;
157  }
158  next = bu_ftell(dbip->dbi_fp);
159  handler(dbip, record.a.a_name, addr, nrec, RT_DIR_SOLID, client_data);
160  break;
161  case ID_ARS_B:
162  bu_log("db_scan ERROR: Unattached ARS 'B' record\n");
163  break;
164  case ID_SOLID:
165  handler(dbip, record.s.s_name, addr, nrec, RT_DIR_SOLID, client_data);
166  break;
167  case DBID_STRSOL:
168  for (; nrec < DB_SS_NGRAN; nrec++) {
169  if (fread((char *)&rec2, sizeof(rec2), 1, dbip->dbi_fp) != 1)
170  break;
171  }
172  next = bu_ftell(dbip->dbi_fp);
173  handler(dbip, record.ss.ss_name, addr, nrec, RT_DIR_SOLID, client_data);
174  break;
175  case ID_MATERIAL:
176  if (do_old_matter) {
177  short lo, hi;
178 
179  if (dbip->dbi_version < 0) {
180  lo = flip_short(record.md.md_low);
181  hi = flip_short(record.md.md_hi);
182  } else {
183  lo = record.md.md_low;
184  hi = record.md.md_hi;
185  }
186 
187  /* This is common to RT and MGED */
188  rt_color_addrec(lo, hi, record.md.md_r, record.md.md_g, record.md.md_b, addr);
189  }
190  break;
191  case ID_P_HEAD:
192  while (1) {
193  here = bu_ftell(dbip->dbi_fp);
194  if (fread((char *)&rec2, sizeof(rec2), 1, dbip->dbi_fp) != 1)
195  break;
196  DEBUG_PR(here, rec2);
197  if (rec2.u_id != ID_P_DATA) {
198  bu_fseek(dbip->dbi_fp, here, 0);
199  break;
200  }
201  nrec++;
202  }
203  next = bu_ftell(dbip->dbi_fp);
204  handler(dbip, record.p.p_name, addr, nrec, RT_DIR_SOLID, client_data);
205  break;
206  case ID_P_DATA:
207  bu_log("db_scan ERROR: Unattached P_DATA record\n");
208  break;
209  case ID_BSOLID:
210  while (1) {
211  /* Find and skip subsequent BSURFs */
212  here = bu_ftell(dbip->dbi_fp);
213  if (fread((char *)&rec2, sizeof(rec2), 1, dbip->dbi_fp) != 1)
214  break;
215  DEBUG_PR(here, rec2);
216  if (rec2.u_id != ID_BSURF) {
217  bu_fseek(dbip->dbi_fp, here, 0);
218  break;
219  }
220 
221  /* Just skip over knots and control mesh */
222  j = (rec2.d.d_nknots + rec2.d.d_nctls);
223  nrec += j+1;
224  while (j-- > 0) {
225  if (fread((char *)&rec2, sizeof(rec2), 1, dbip->dbi_fp) != 1)
226  break;
227  }
228  next = bu_ftell(dbip->dbi_fp);
229  }
230  handler(dbip, record.B.B_name, addr, nrec, RT_DIR_SOLID, client_data);
231  break;
232  case ID_BSURF:
233  bu_log("db_scan ERROR: Unattached B-spline surface record\n");
234 
235  /* Just skip over knots and control mesh */
236  j = (record.d.d_nknots + record.d.d_nctls);
237  nrec += j;
238  while (j-- > 0) {
239  if (fread((char *)&rec2, sizeof(rec2), 1, dbip->dbi_fp) != 1)
240  break;
241  }
242  break;
243  case DBID_ARBN:
244  j = ntohl(*(uint32_t *)record.n.n_grans);
245  nrec += j;
246  while (j-- > 0) {
247  if (fread((char *)&rec2, sizeof(rec2), 1, dbip->dbi_fp) != 1)
248  break;
249  }
250  next = bu_ftell(dbip->dbi_fp);
251  handler(dbip, record.n.n_name, addr, nrec, RT_DIR_SOLID, client_data);
252  break;
253  case DBID_PARTICLE:
254  handler(dbip, record.part.p_name, addr, nrec, RT_DIR_SOLID, client_data);
255  break;
256  case DBID_PIPE:
257  j = ntohl(*(uint32_t *)record.pwr.pwr_count);
258  nrec += j;
259  while (j-- > 0) {
260  if (fread((char *)&rec2, sizeof(rec2), 1, dbip->dbi_fp) != 1)
261  break;
262  }
263  next = bu_ftell(dbip->dbi_fp);
264  handler(dbip, record.pwr.pwr_name, addr, nrec, RT_DIR_SOLID, client_data);
265  break;
266  case DBID_NMG:
267  j = ntohl(*(uint32_t *)record.nmg.N_count);
268  nrec += j;
269  while (j-- > 0) {
270  if (fread((char *)&rec2, sizeof(rec2), 1, dbip->dbi_fp) != 1)
271  break;
272  }
273  next = bu_ftell(dbip->dbi_fp);
274  handler(dbip, record.nmg.N_name, addr, nrec, RT_DIR_SOLID, client_data);
275  break;
276  case DBID_SKETCH:
277  j = ntohl(*(uint32_t *)record.skt.skt_count);
278  nrec += j;
279  while (j-- > 0) {
280  if (fread((char *)&rec2, sizeof(rec2), 1, dbip->dbi_fp) != 1)
281  break;
282  }
283  next = bu_ftell(dbip->dbi_fp);
284  handler(dbip, record.skt.skt_name, addr, nrec, RT_DIR_SOLID, client_data);
285  break;
286  case DBID_EXTR:
287  j = ntohl(*(uint32_t *)record.extr.ex_count);
288  nrec += j;
289  while (j-- > 0) {
290  if (fread((char *)&rec2, sizeof(rec2), 1, dbip->dbi_fp) != 1)
291  break;
292  }
293  next = bu_ftell(dbip->dbi_fp);
294  handler(dbip, record.extr.ex_name, addr, nrec, RT_DIR_SOLID, client_data);
295  break;
296  case DBID_CLINE:
297  handler(dbip, record.s.s_name, addr, nrec, RT_DIR_SOLID, client_data);
298  break;
299  case DBID_BOT:
300  j = ntohl(*(uint32_t *)record.bot.bot_nrec);
301  nrec += j;
302  while (j-- > 0) {
303  if (fread((char *)&rec2, sizeof(rec2), 1, dbip->dbi_fp) != 1)
304  break;
305  }
306  next = bu_ftell(dbip->dbi_fp);
307  handler(dbip, record.s.s_name, addr, nrec, RT_DIR_SOLID, client_data);
308  break;
309  case ID_MEMB:
310  bu_log("db_scan ERROR: Unattached combination MEMBER record\n");
311  break;
312  case ID_COMB:
313  while (1) {
314  here = bu_ftell(dbip->dbi_fp);
315  if (fread((char *)&rec2, sizeof(rec2), 1, dbip->dbi_fp) != 1)
316  break;
317  DEBUG_PR(here, rec2);
318  if (rec2.u_id != ID_MEMB) {
319  bu_fseek(dbip->dbi_fp, here, 0);
320  break;
321  }
322  nrec++;
323  }
324  next = bu_ftell(dbip->dbi_fp);
325  switch (record.c.c_flags) {
326  default:
327  case DBV4_NON_REGION:
328  j = RT_DIR_COMB;
329  break;
330  case DBV4_REGION:
331  case DBV4_REGION_FASTGEN_PLATE:
332  case DBV4_REGION_FASTGEN_VOLUME:
334  break;
335  }
336  handler(dbip, record.c.c_name, addr, nrec, j, client_data);
337  break;
338  default:
339  bu_log("db_scan ERROR: bad record %c (0%o), addr=%ld\n",
340  record.u_id, record.u_id, addr);
341  /* skip this record */
342  break;
343  }
344  totrec += nrec;
345  }
346  dbip->dbi_nrec = totrec;
347  next = bu_ftell(dbip->dbi_fp);
348  if (next < 0)
349  dbip->dbi_eof = -1;
350  else
351  dbip->dbi_eof = next;
352  rewind(dbip->dbi_fp);
353 
354  return 0; /* OK */
355 }
356 
357 
358 /**
359  * Update the existing v4 IDENT record with new title and units. To
360  * permit using db_get and db_put, a custom directory entry is
361  * crafted.
362  *
363  * Note: Special care is required, because the "title" arg may
364  * actually be passed in as dbip->dbi_title.
365  */
366 int
367 db_update_ident(struct db_i *dbip, const char *new_title, double local2mm)
368 {
369  int put;
370  struct directory dir;
371  union record rec;
372  char *old_title;
373  int v4units;
374  const char *ident = "/IDENT/";
375 
376  RT_CK_DBI(dbip);
377 
378  if (!new_title)
379  new_title = "";
380 
381  if (RT_G_DEBUG&DEBUG_DB)
382  bu_log("db_update_ident(%p, '%s', %g)\n", (void *)dbip, new_title, local2mm);
383 
384  /* make sure dbip is a valid version */
385  if (db_version(dbip) <= 0) {
386  bu_log("Invalid geometry database write request encountered.\n"
387  "Converting to READ-ONLY mode.\n");
388  dbip->dbi_read_only = 1;
389  return -1;
390  }
391 
392  /* assume it's a v5 */
393  if (db_version(dbip) > 4)
394  return db5_update_ident(dbip, new_title, local2mm);
395 
396  RT_DIR_SET_NAMEP(&dir, ident);
397  dir.d_addr = (off_t)0L;
398  dir.d_len = 1;
399  dir.d_magic = RT_DIR_MAGIC;
400  dir.d_flags = 0;
401  if (db_get(dbip, &dir, &rec, 0, 1) < 0 ||
402  rec.u_id != ID_IDENT) {
403  bu_log("db_update_ident() corrupted database header!\n");
404  dbip->dbi_read_only = 1;
405  return -1;
406  }
407 
408  rec.i.i_title[0] = '\0';
409  bu_strlcpy(rec.i.i_title, new_title, sizeof(rec.i.i_title));
410 
411  old_title = dbip->dbi_title;
412  dbip->dbi_title = bu_strdup(new_title);
413 
414  if ((v4units = db_v4_get_units_code(bu_units_string(local2mm))) < 0) {
415  bu_log("db_update_ident(): \
416 Due to a restriction in previous versions of the BRL-CAD database format, your\n\
417 editing units %g will not be remembered on your next editing session.\n\
418 This will not harm the integrity of your database.\n\
419 You may wish to consider upgrading your database using \"dbupgrade\".\n",
420  local2mm);
421  v4units = ID_MM_UNIT;
422  }
423  rec.i.i_units = v4units;
424 
425  if (old_title)
426  bu_free(old_title, "old dbi_title");
427 
428  put = db_put(dbip, &dir, &rec, 0, 1);
429  return put;
430 }
431 
432 
433 /**
434  * Fwrite an IDENT record with given title and editing scale.
435  * Attempts to map the editing scale into a v4 database unit as best
436  * it can. No harm done if it doesn't map.
437  *
438  * This should be called by db_create() only.
439  * All others should call db_update_ident().
440  *
441  * Returns -
442  * 0 Success
443  * -1 Fatal Error
444  */
445 int
446 db_fwrite_ident(FILE *fp, const char *title, double local2mm)
447 {
448  union record rec;
449  int code;
450 
451  code = db_v4_get_units_code(bu_units_string(local2mm));
452 
453  if (RT_G_DEBUG&DEBUG_DB) bu_log("db_fwrite_ident(%p, '%s', %g) code=%d\n",
454  (void *)fp, title, local2mm, code);
455 
456  memset((char *)&rec, 0, sizeof(rec));
457  rec.i.i_id = ID_IDENT;
458  rec.i.i_units = code;
459  bu_strlcpy(rec.i.i_version, ID_VERSION, sizeof(rec.i.i_version));
460  bu_strlcpy(rec.i.i_title, title, sizeof(rec.i.i_title));
461 
462  if (fwrite((char *)&rec, sizeof(rec), 1, fp) != 1)
463  return -1;
464  return 0;
465 }
466 
467 void
468 db_conversions(struct db_i *dbip, int local)
469 
470 /* one of ID_??_UNIT */
471 {
472  RT_CK_DBI(dbip);
473 
474  /* Base unit is MM */
475  switch (local) {
476 
477  case ID_NO_UNIT:
478  /* no local unit specified ... use the base unit */
479  dbip->dbi_local2base = 1.0;
480  break;
481 
482  case ID_UM_UNIT:
483  /* local unit is um */
484  dbip->dbi_local2base = 0.001; /* UM to MM */
485  break;
486 
487  case ID_MM_UNIT:
488  /* local unit is mm */
489  dbip->dbi_local2base = 1.0;
490  break;
491 
492  case ID_CM_UNIT:
493  /* local unit is cm */
494  dbip->dbi_local2base = 10.0; /* CM to MM */
495  break;
496 
497  case ID_M_UNIT:
498  /* local unit is meters */
499  dbip->dbi_local2base = 1000.0; /* M to MM */
500  break;
501 
502  case ID_KM_UNIT:
503  /* local unit is km */
504  dbip->dbi_local2base = 1000000.0; /* KM to MM */
505  break;
506 
507  case ID_IN_UNIT:
508  /* local unit is inches */
509  dbip->dbi_local2base = 25.4; /* IN to MM */
510  break;
511 
512  case ID_FT_UNIT:
513  /* local unit is feet */
514  dbip->dbi_local2base = 304.8; /* FT to MM */
515  break;
516 
517  case ID_YD_UNIT:
518  /* local unit is yards */
519  dbip->dbi_local2base = 914.4; /* YD to MM */
520  break;
521 
522  case ID_MI_UNIT:
523  /* local unit is miles */
524  dbip->dbi_local2base = 1609344; /* MI to MM */
525  break;
526 
527  default:
528  dbip->dbi_local2base = 1.0;
529  break;
530  }
531  dbip->dbi_base2local = 1.0 / dbip->dbi_local2base;
532 }
533 
534 int
535 db_v4_get_units_code(const char *str)
536 {
537  if (!str) return ID_NO_UNIT; /* no units specified */
538 
539  if (BU_STR_EQUAL(str, "mm") || BU_STR_EQUAL(str, "millimeters"))
540  return ID_MM_UNIT;
541  if (BU_STR_EQUAL(str, "um") || BU_STR_EQUAL(str, "micrometers"))
542  return ID_UM_UNIT;
543  if (BU_STR_EQUAL(str, "cm") || BU_STR_EQUAL(str, "centimeters"))
544  return ID_CM_UNIT;
545  if (BU_STR_EQUAL(str, "m") || BU_STR_EQUAL(str, "meters"))
546  return ID_M_UNIT;
547  if (BU_STR_EQUAL(str, "km") || BU_STR_EQUAL(str, "kilometers"))
548  return ID_KM_UNIT;
549  if (BU_STR_EQUAL(str, "in") || BU_STR_EQUAL(str, "inches") || BU_STR_EQUAL(str, "inch"))
550  return ID_IN_UNIT;
551  if (BU_STR_EQUAL(str, "ft") || BU_STR_EQUAL(str, "feet") || BU_STR_EQUAL(str, "foot"))
552  return ID_FT_UNIT;
553  if (BU_STR_EQUAL(str, "yd") || BU_STR_EQUAL(str, "yards") || BU_STR_EQUAL(str, "yard"))
554  return ID_YD_UNIT;
555  if (BU_STR_EQUAL(str, "mi") || BU_STR_EQUAL(str, "miles") || BU_STR_EQUAL(str, "mile"))
556  return ID_MI_UNIT;
557 
558  return -1; /* error */
559 }
560 
561 
562 /** @} */
563 /*
564  * Local Variables:
565  * mode: C
566  * tab-width: 8
567  * indent-tabs-mode: t
568  * c-file-style: "stroustrup"
569  * End:
570  * ex: shiftwidth=4 tabstop=8
571  */
Definition: raytrace.h:800
void bu_log(const char *,...) _BU_ATTR_PRINTF12
Definition: log.c:176
#define RT_DIR_SET_NAMEP(_dp, _name)
Definition: raytrace.h:901
off_t bu_ftell(FILE *stream)
Definition: file.c:344
size_t d_len
of db granules used
Definition: raytrace.h:867
int db_scan(struct db_i *dbip, int(*handler)(struct db_i *, const char *, off_t, size_t, int, void *), int do_old_matter, void *client_data)
Definition: db_scan.c:73
void db_conversions(struct db_i *dbip, int local)
Definition: db_scan.c:468
int bu_fseek(FILE *stream, off_t offset, int origin)
Definition: file.c:330
int db_version(struct db_i *dbip)
Definition: db5_scan.c:414
Header file for the BRL-CAD common definitions.
uint32_t d_magic
Magic number.
Definition: raytrace.h:858
#define RT_DIR_REGION
region
Definition: raytrace.h:885
int db_update_ident(struct db_i *dbip, const char *new_title, double local2mm)
Definition: db_scan.c:367
int db_get(const struct db_i *, const struct directory *dp, union record *where, off_t offset, size_t len)
Definition: db_io.c:134
void rt_color_addrec(int low, int hi, int r, int g, int b, off_t addr)
Definition: mater.c:176
size_t dbi_nrec
PRIVATE: # records after db_scan()
Definition: raytrace.h:817
void * memset(void *s, int c, size_t n)
int db_v4_get_units_code(const char *str)
Definition: db_scan.c:535
#define RT_G_DEBUG
Definition: raytrace.h:1718
char * dbi_title
title from IDENT rec
Definition: raytrace.h:809
#define RT_DIR_SOLID
this name is a solid
Definition: raytrace.h:883
off_t dbi_eof
PRIVATE: End+1 pos after db_scan()
Definition: raytrace.h:816
#define bu_strlcpy(dst, src, size)
Definition: str.h:60
struct mem_map * dbi_freep
PRIVATE: map of free granules.
Definition: raytrace.h:819
FILE * dbi_fp
PRIVATE: object hash table.
Definition: raytrace.h:815
double dbi_base2local
unit conversion factors
Definition: raytrace.h:808
int db_fwrite_ident(FILE *fp, const char *title, double local2mm)
Definition: db_scan.c:446
#define DEBUG_PR(aaa, rrr)
Definition: db_scan.c:44
#define RT_CK_DBI(_p)
Definition: raytrace.h:829
int dbi_read_only
!0 => read only file
Definition: raytrace.h:806
HIDDEN int code(fastf_t x, fastf_t y)
Definition: clip.c:43
const char * bu_units_string(const double mm)
#define RT_DIR_COMB
combination
Definition: raytrace.h:884
#define DEBUG_DB
5 Database debugging
Definition: raytrace.h:88
int db_put(struct db_i *, const struct directory *dp, union record *where, off_t offset, size_t len)
Definition: db_io.c:212
short flip_short(short s)
Definition: db_flip.c:47
int dbi_version
PRIVATE: use db_version()
Definition: raytrace.h:824
void bu_free(void *ptr, const char *str)
Definition: malloc.c:328
#define RT_DIR_MAGIC
Definition: magic.h:159
double dbi_local2base
local2mm
Definition: raytrace.h:807
void rt_memfree(struct mem_map **pp, size_t size, off_t addr)
Definition: memalloc.c:228
int db5_update_ident(struct db_i *dbip, const char *title, double local2mm)
Definition: attributes.c:381
int d_flags
flags
Definition: raytrace.h:869
#define bu_strdup(s)
Definition: str.h:71
#define BU_STR_EQUAL(s1, s2)
Definition: str.h:126