GRASS GIS 8 Programmer's Manual  8.2.1dev(2022)-f67d8e515
map.c
Go to the documentation of this file.
1 /*!
2  \file lib/vector/Vlib/map.c
3 
4  \brief Vector library - Manipulate vector map (copy, rename, delete)
5 
6  Higher level functions for reading/writing/manipulating vectors.
7 
8  (C) 2001-2009, 2012 by the GRASS Development Team
9 
10  This program is free software under the GNU General Public License
11  (>=v2). Read the file COPYING that comes with GRASS for details.
12 
13  \author Original author CERL, probably Dave Gerdes or Mike Higgins.
14  \author Update to GRASS 5.7 Radim Blazek and David D. Gray.
15 */
16 
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <dirent.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <fcntl.h>
25 #include <errno.h>
26 
27 #include <grass/glocale.h>
28 #include <grass/vector.h>
29 #include <grass/dbmi.h>
30 #include <grass/glocale.h>
31 
32 #include "local_proto.h"
33 
34 /*!
35  \brief Creates and initializes Map_info structure
36 
37  To free allocated memory call Vect_destroy_map_struct().
38 
39  \return pointer to Map_info
40  */
42 {
43  struct Map_info *p;
44 
45  p = (struct Map_info *)malloc(sizeof(struct Map_info));
46 
47  if (NULL == p)
48  G_fatal_error("Vect_new_map_struct(): %s", _("Out of memory"));
49 
50  G_zero(p, sizeof(struct Map_info));
51 
52  return p;
53 }
54 
55 /*!
56  \brief Frees all memory associated with a Map_info structure,
57  including the structure itself
58 
59  \param p pointer to Map_info structure
60  */
62 {
63  /* We should free all allocated member structures, but they may be already
64  freed by other functions (e.g. Vect_close()) without resetting member pointers to zero */
65 
66  G_free((char *)p);
67 }
68 
69 /*!
70  \brief Copy file
71 
72  \param src source file
73  \param[out] dst destination file
74 
75  \return 0 OK
76  \return 1 error
77  */
78 static int copy_file(const char *src, const char *dst)
79 {
80  char buf[4096];
81  int fd, fd2;
82  FILE *f2;
83  int len, len2;
84 
85  if ((fd = open(src, O_RDONLY)) < 0)
86  return 1;
87 
88  /* if((fd2 = open(dst, O_CREAT|O_TRUNC|O_WRONLY)) < 0) { */
89  if ((f2 = fopen(dst, "w")) == NULL) {
90  close(fd);
91  return 1;
92  }
93 
94  fd2 = fileno(f2);
95 
96  len2 = 0;
97  while ((len = read(fd, buf, 4096)) > 0) {
98  while (len && (len2 = write(fd2, buf, len)) >= 0)
99  len -= len2;
100  }
101 
102  close(fd);
103  /* close(fd2); */
104  fclose(f2);
105 
106  if (len == -1 || len2 == -1)
107  return 1;
108 
109  return 0;
110 }
111 
112 /*!
113  \brief Copy vector map including attribute tables
114 
115  Note: Output vector map is overwritten if exists!
116 
117  \param in name if vector map to be copied
118  \param mapset mapset name where the input map is located
119  \param out name for output vector map (new map is created in current mapset)
120 
121  \return -1 error
122  \return 0 success
123  */
124 int Vect_copy(const char *in, const char *mapset, const char *out)
125 {
126  int i, ret;
127  struct Map_info In, Out;
128  char old_path[GPATH_MAX], new_path[GPATH_MAX], buf[GPATH_MAX];
129  const char *files[] = { GV_FRMT_ELEMENT, GV_COOR_ELEMENT,
132  NULL
133  };
134  const char *inmapset;
135  char xname[GNAME_MAX], xmapset[GMAPSET_MAX];
136 
137  G_debug(2, "Copy vector '%s' in '%s' to '%s'", in, mapset, out);
138  /* check for [A-Za-z][A-Za-z0-9_]* in name */
139  if (Vect_legal_filename(out) < 0)
140  G_fatal_error(_("Vector map name is not SQL compliant"));
141 
142  inmapset = G_find_vector2(in, mapset);
143  if (!inmapset) {
144  G_warning(_("Unable to find vector map <%s> in <%s>"), in, mapset);
145  return -1;
146  }
147  mapset = inmapset;
148 
149  /* remove mapset from fully qualified name, confuses G_file_name() */
150  if (G_name_is_fully_qualified(in, xname, xmapset)) {
151  in = xname;
152  }
153 
154  /* Delete old vector if it exists */
155  if (G_find_vector2(out, G_mapset())) {
156  G_warning(_("Vector map <%s> already exists and will be overwritten"),
157  out);
158  ret = Vect_delete(out);
159  if (ret != 0) {
160  G_warning(_("Unable to delete vector map <%s>"), out);
161  return -1;
162  }
163  }
164 
165  /* Copy the directory */
167 
168  i = 0;
169  while (files[i]) {
170  sprintf(buf, "%s/%s", in, files[i]);
171  G_file_name(old_path, GV_DIRECTORY, buf, mapset);
172  sprintf(buf, "%s/%s", out, files[i]);
173  G_file_name(new_path, GV_DIRECTORY, buf, G_mapset());
174 
175  if (access(old_path, F_OK) == 0) { /* file exists? */
176  G_debug(2, "copy %s to %s", old_path, new_path);
177  if (copy_file(old_path, new_path)) {
178  G_warning(_("Unable to copy vector map <%s> to <%s>"),
179  old_path, new_path);
180  }
181  }
182  i++;
183  }
184 
185  G_file_name(old_path, GV_DIRECTORY, in, mapset);
186  G_file_name(new_path, GV_DIRECTORY, out, G_mapset());
187 
188  /* Open input */
190  if (Vect_open_old_head(&In, in, mapset) < 0)
191  G_fatal_error(_("Unable to open vector map <%s>"), in);
192 
193  if (In.format != GV_FORMAT_NATIVE) { /* Done */
194  Vect_close(&In);
195  return 0;
196  }
197 
198  /* Open output */
200  if (Vect_open_update_head(&Out, out, G_mapset()) < 0)
201  G_fatal_error(_("Unable to open vector map <%s>"), out);
202 
203  /* Copy tables */
204  if (Vect_copy_tables(&In, &Out, 0) != 0) {
205  Vect_close(&In);
206  Vect_close(&Out);
207 
208  return 1;
209  }
210 
211  Vect_close(&In);
212  Vect_close(&Out);
213 
214  return 0;
215 }
216 
217 /*!
218  \brief Rename existing vector map (in the current mapset).
219 
220  Attribute tables are created in the same database where input tables were stored.
221 
222  The origial format (native/OGR) is used.
223 
224  Note: Output vector map is overwritten if exists!
225 
226  \param in name of vector map to be renamed
227  \param out name for output vector map
228 
229  \return -1 error
230  \return 0 success
231  */
232 int Vect_rename(const char *in, const char *out)
233 {
234  int i, n, ret, type;
235  struct Map_info Map;
236  struct field_info *Fin, *Fout;
237  int *fields;
238  dbDriver *driver;
239  char xname[GNAME_MAX], xmapset[GMAPSET_MAX];
240 
241  G_debug(2, "Rename vector '%s' to '%s'", in, out);
242  /* check for [A-Za-z][A-Za-z0-9_]* in name */
243  if (Vect_legal_filename(out) < 0)
244  G_fatal_error(_("Vector map name is not SQL compliant"));
245 
246  /* Delete old vector if it exists */
247  if (G_find_vector2(out, G_mapset())) {
248  G_warning(_("Vector map <%s> already exists and will be overwritten"),
249  out);
250  Vect_delete(out);
251  }
252 
253  /* remove mapset from fully qualified name */
254  if (G_name_is_fully_qualified(in, xname, xmapset)) {
255  in = xname;
256  }
257 
258  /* Move the directory */
259  ret = G_rename(GV_DIRECTORY, in, out);
260 
261  if (ret == 0) {
262  G_warning(_("Vector map <%s> not found"), in);
263  return -1;
264  }
265  else if (ret == -1) {
266  G_warning(_("Unable to copy vector map <%s> to <%s>"), in, out);
267  return -1;
268  }
269 
270  /* Rename all tables if the format is native */
272  if (Vect_open_update_head(&Map, out, G_mapset()) < 0)
273  G_fatal_error(_("Unable to open vector map <%s>"), out);
274 
275  if (Map.format != GV_FORMAT_NATIVE) { /* Done */
276  Vect_close(&Map);
277  return 0;
278  }
279 
280  /* Copy tables */
281  n = Vect_get_num_dblinks(&Map);
282  type = GV_1TABLE;
283  if (n > 1)
284  type = GV_MTABLE;
285 
286  /* Make the list of fields */
287  fields = (int *)G_malloc(n * sizeof(int));
288 
289  for (i = 0; i < n; i++) {
290  Fin = Vect_get_dblink(&Map, i);
291  fields[i] = Fin->number;
292  }
293 
294  for (i = 0; i < n; i++) {
295  G_debug(3, "field[%d] = %d", i, fields[i]);
296 
297  Fin = Vect_get_field(&Map, fields[i]);
298  if (Fin == NULL) {
299  G_warning(_("Database connection not defined for layer %d"),
300  fields[i]);
301  Vect_close(&Map);
302  return -1;
303  }
304 
305  Fout = Vect_default_field_info(&Map, Fin->number, Fin->name, type);
306  G_debug(3, "Copy drv:db:table '%s:%s:%s' to '%s:%s:%s'",
307  Fin->driver, Fin->database, Fin->table, Fout->driver,
308  Fout->database, Fout->table);
309 
310  /* TODO: db_rename_table instead of db_copy_table */
311  ret = db_copy_table(Fin->driver, Fin->database, Fin->table,
312  Fout->driver, Vect_subst_var(Fout->database,
313  &Map), Fout->table);
314 
315  if (ret == DB_FAILED) {
316  G_warning(_("Unable to copy table <%s>"), Fin->table);
317  Vect_close(&Map);
318  return -1;
319  }
320 
321  /* Change the link */
322  Vect_map_del_dblink(&Map, Fin->number);
323 
324  Vect_map_add_dblink(&Map, Fout->number, Fout->name, Fout->table,
325  Fin->key, Fout->database, Fout->driver);
326 
327  /* Delete old table */
328  ret = db_delete_table(Fin->driver, Fin->database, Fin->table);
329  if (ret == DB_FAILED) {
330  G_warning(_("Unable to delete table <%s>"), Fin->table);
331  Vect_close(&Map);
332  return -1;
333  }
334 
335  driver =
337  Vect_subst_var(Fout->database,
338  &Map));
339  if (driver == NULL) {
340  G_warning(_("Unable to open database <%s> by driver <%s>"),
341  Fout->database, Fout->driver);
342  }
343  else {
344  if (db_create_index2(driver, Fout->table, Fin->key) != DB_OK)
345  G_warning(_("Unable to create index for table <%s>, key <%s>"),
346  Fout->table, Fout->key);
347 
349  }
350  }
351 
352  Vect_close(&Map);
353  free(fields);
354 
355  return 0;
356 }
357 
358 /*!
359  \brief Delete vector map including attribute tables
360 
361  Vector map must be located in current mapset.
362 
363  \param map name of vector map to be delete
364 
365  \return -1 error
366  \return 0 success
367  */
368 int Vect_delete(const char *map)
369 {
370  return Vect__delete(map, FALSE);
371 }
372 
373 /*!
374  \brief Delete vector map (internal use only)
375 
376  \param map name of vector map to be delete
377  \param is_tmp TRUE for temporary maps
378 
379  \return -1 error
380  \return 0 success
381 */
382 int Vect__delete(const char *map, int is_tmp)
383 {
384  int ret;
385  char path[GPATH_MAX], path_buf[GPATH_MAX];
386  char xname[GNAME_MAX], xmapset[GMAPSET_MAX];
387  const char *tmp, *mapset, *env;
388 
389  struct Map_info Map;
390 
391  DIR *dir;
392  struct dirent *ent;
393 
394  G_debug(3, "Delete vector '%s' (is_tmp = %d)", map, is_tmp);
395 
396  mapset = G_mapset();
397 
398  /* remove mapset from fully qualified name */
399  if (G_name_is_fully_qualified(map, xname, xmapset)) {
400  if (strcmp(mapset, xmapset) != 0)
401  G_warning(_("Ignoring invalid mapset: %s"), xmapset);
402  map = xname;
403  }
404 
405  if (map == NULL || strlen(map) == 0) {
406  G_warning(_("Invalid vector map name <%s>"), map ? map : "null");
407  return -1;
408  }
409 
410  Vect_set_open_level(1); /* Topo not needed */
411  ret = Vect__open_old(&Map, map, mapset, NULL, FALSE, TRUE, is_tmp);
412  if (ret < 1) {
413  if (is_tmp)
414  return 0; /* temporary vector map doesn't exist */
415  else {
416  G_warning(_("Unable to open header file for vector map <%s>"),
417  map);
418  return -1;
419  }
420  }
421 
423  G_debug(1, "dbln file: %s", path);
424 
425  if (access(path, F_OK) == 0) {
426  int i, n;
427  struct field_info *Fi;
428 
429  /* Delete all tables, NOT external (OGR) */
430  if (Map.format == GV_FORMAT_NATIVE) {
431  n = Vect_get_num_dblinks(&Map);
432  for (i = 0; i < n; i++) {
433  Fi = Vect_get_dblink(&Map, i);
434  if (Fi == NULL) {
435  G_warning(_("Database connection not defined for layer %d"),
436  Map.dblnk->field[i].number);
437  /*
438  Vect_close(&Map);
439  return -1;
440  */
441  continue;
442  }
443  G_debug(3, "Delete drv:db:table '%s:%s:%s'", Fi->driver,
444  Fi->database, Fi->table);
445 
446  ret = db_table_exists(Fi->driver, Fi->database, Fi->table);
447  if (ret == -1) {
448  G_warning(_("Unable to find table <%s> linked to vector map <%s>"),
449  Fi->table, map);
450  /*
451  Vect_close(&Map);
452  return -1;
453  */
454  continue;
455  }
456 
457  if (ret == 1) {
458  ret =
459  db_delete_table(Fi->driver, Fi->database, Fi->table);
460  if (ret == DB_FAILED) {
461  G_warning(_("Unable to delete table <%s>"),
462  Fi->table);
463  /*
464  Vect_close(&Map);
465  return -1;
466  */
467  continue;
468  }
469  }
470  else {
471  G_warning(_("Table <%s> linked to vector map <%s> does not exist"),
472  Fi->table, map);
473  }
474  }
475  }
476  }
477 
478  /* Delete all files from vector/name directory */
479  Vect__get_element_path(path, &Map, NULL);
480  Vect_close(&Map);
481  G_debug(3, "opendir '%s'", path);
482  dir = opendir(path);
483  if (dir == NULL) {
484  G_warning(_("Unable to open directory '%s'"), path);
485  return -1;
486  }
487 
488  while ((ent = readdir(dir))) {
489  G_debug(3, "file = '%s'", ent->d_name);
490  if ((strcmp(ent->d_name, ".") == 0) ||
491  (strcmp(ent->d_name, "..") == 0))
492  continue;
493 
494  ret = snprintf(path_buf, GPATH_MAX, "%s/%s", path, ent->d_name);
495  if (ret >= GPATH_MAX) {
496  G_warning(_("Filepath '%s/%s' exceeds max length"), path,
497  ent->d_name);
498  closedir(dir);
499  return -1;
500  }
501  G_debug(3, "delete file '%s'", path_buf);
502  ret = unlink(path_buf);
503  if (ret == -1) {
504  G_warning(_("Unable to delete file '%s'"), path_buf);
505  closedir(dir);
506  return -1;
507  }
508  }
509  closedir(dir);
510 
511  env = getenv("GRASS_VECTOR_TMPDIR_MAPSET");
512  if (env && strcmp(env, "0") == 0) {
513  tmp = path;
514  }
515  else {
516  /* NFS can create .nfsxxxxxxxx files for those deleted
517  * -> we have to move the directory to ./tmp before it is deleted */
518  tmp = G_tempfile();
519 
520  G_debug(3, "rename '%s' to '%s'", path, tmp);
521 
522  ret = rename(path, tmp);
523  if (ret == -1) {
524  G_warning(_("Unable to rename directory '%s' to '%s'"), path, tmp);
525  return -1;
526  }
527  }
528 
529  G_debug(3, "remove directory '%s'", tmp);
530  /* Warning: remove() fails on Windows */
531  ret = rmdir(tmp);
532  if (ret == -1) {
533  G_warning(_("Unable to remove directory '%s': %s"),
534  tmp, strerror(errno));
535  return -1;
536  }
537 
538  return 0;
539 }
540 
541 /*!
542  \brief Set spatial index to be realease when vector is closed.
543 
544  By default, the memory occupied by spatial index is not released.
545 
546  \param Map vector map
547  */
549 {
550  Map->plus.release_support = TRUE;
551 }
552 
553 /*!
554  \brief Set category index to be updated when vector is changed.
555 
556  By default, category index is not updated if vector is changed,
557  this function sets category index update.
558 
559  WARNING: currently only category for elements is updated not for
560  areas
561 
562  \param Map vector map
563 */
565 {
566  Map->plus.update_cidx = TRUE;
567 }
#define TRUE
Definition: gis.h:59
#define G_malloc(n)
Definition: defs/gis.h:112
int Vect_rename(const char *in, const char *out)
Rename existing vector map (in the current mapset).
Definition: map.c:232
DIR * opendir()
char * G_file_name(char *, const char *, const char *, const char *)
Builds full path names to GIS data files.
Definition: file_name.c:61
void void void void G_fatal_error(const char *,...) __attribute__((format(printf
#define GMAPSET_MAX
Definition: gis.h:178
int open
Open indicator.
Definition: dig_structs.h:1296
char * Vect__get_element_path(char *file_path, const struct Map_info *Map, const char *element)
Get map element full path (internal use only)
#define GV_DIRECTORY
Name of vector directory.
Definition: dig_defines.h:8
#define GV_MTABLE
More tables linked to vector map.
Definition: dig_defines.h:101
int Vect_open_update_head(struct Map_info *, const char *, const char *)
Open header file of existing vector map for updating (mostly for database link updates) ...
#define GV_FRMT_ELEMENT
Format description, data location (OGR)
Definition: dig_defines.h:10
int Vect__open_old(struct Map_info *Map, const char *name, const char *mapset, const char *layer, int update, int head_only, int is_tmp)
Open existing vector map for reading (internal use only)
const char * G_find_vector2(const char *, const char *)
Find a vector map (look but don&#39;t touch)
Definition: find_vect.c:62
#define GV_FORMAT_NATIVE
Geometry data formats supported by lib Don&#39;t change GV_FORMAT_* values, this order is hardcoded in li...
Definition: dig_defines.h:83
#define GV_DBLN_ELEMENT
Native format, link to database.
Definition: dig_defines.h:16
int Vect_map_add_dblink(struct Map_info *, int, const char *, const char *, const char *, const char *, const char *)
Add new db connection to Map_info structure.
Definition: field.c:113
int release_support
Release memory occupied by support structures (topo, spatial, category)
Definition: dig_structs.h:838
char * table
Name of DB table.
Definition: dig_structs.h:155
void Vect_set_category_index_update(struct Map_info *Map)
Set category index to be updated when vector is changed.
Definition: map.c:564
void G_free(void *)
Free allocated memory.
Definition: gis/alloc.c:149
#define GV_TOPO_ELEMENT
Native format, topology file.
Definition: dig_defines.h:20
#define GV_HEAD_ELEMENT
Native format, header information.
Definition: dig_defines.h:14
char * dst
Definition: lz4.h:599
dbDriver * db_start_driver_open_database(const char *, const char *)
Open driver/database connection.
Definition: db.c:28
int db_delete_table(const char *, const char *, const char *)
Delete table.
Definition: delete_tab.c:29
int Vect_close(struct Map_info *)
Close vector map.
void free(void *)
#define NULL
Definition: ccmath.h:32
int Vect__delete(const char *map, int is_tmp)
Delete vector map (internal use only)
Definition: map.c:382
int type
Feature type constraint.
Definition: dig_structs.h:1374
int db_close_database_shutdown_driver(dbDriver *)
Close driver/database connection.
Definition: db.c:62
struct field_info * Vect_get_field(const struct Map_info *, int)
Get information about link to database (by layer number)
Definition: field.c:507
int Vect_get_num_dblinks(const struct Map_info *)
Get number of defined dblinks.
Definition: level_two.c:163
char * database
Definition: dig_structs.h:151
dir_entry * readdir()
void * malloc(YYSIZE_T)
Layer (old: field) information.
Definition: dig_structs.h:134
char * name
Layer name (optional)
Definition: dig_structs.h:143
#define GV_CIDX_ELEMENT
Native format, category index.
Definition: dig_defines.h:24
char * Vect_subst_var(const char *, const struct Map_info *)
Substitute variable in string.
Definition: field.c:1004
#define FALSE
Definition: gis.h:63
char * G_tempfile(void)
Returns a temporary file name.
Definition: tempfile.c:62
struct field_info * Vect_get_dblink(const struct Map_info *, int)
Get information about link to database.
Definition: field.c:467
struct Map_info * Vect_new_map_struct(void)
Creates and initializes Map_info structure.
Definition: map.c:41
#define GV_HIST_ELEMENT
Native format, history file.
Definition: dig_defines.h:18
int G_make_mapset_dir_object(const char *, const char *)
Create directory for an object of a given type.
Definition: mapset_msc.c:108
const struct driver * driver
Definition: driver/init.c:25
struct Plus_head plus
Plus info (topology, version, ...)
Definition: dig_structs.h:1286
int Vect_map_del_dblink(struct Map_info *, int)
Delete db connection from Map_info structure.
Definition: field.c:153
int Vect_delete(const char *map)
Delete vector map including attribute tables.
Definition: map.c:368
#define GPATH_MAX
Definition: gis.h:180
#define GV_1TABLE
One table linked to vector map.
Definition: dig_defines.h:99
int Vect_legal_filename(const char *)
Check if output is legal vector name.
Definition: legal_vname.c:31
int G_rename(const char *, const char *, const char *)
Rename a database file.
Definition: rename.c:70
char * mapset
Mapset name.
Definition: dig_structs.h:1336
int Vect_set_open_level(int)
Predetermine level at which a vector map will be opened for reading.
Vector map info.
Definition: dig_structs.h:1259
int Vect_open_old_head(struct Map_info *, const char *, const char *)
Reads only info about vector map (headers)
int db_create_index2(dbDriver *, const char *, const char *)
Create unique index.
Definition: c_create_idx.c:61
struct dblinks * dblnk
Array of DB links.
Definition: dig_structs.h:1281
char * driver
Name of DB driver (&#39;sqlite&#39;, &#39;dbf&#39;, ...)
Definition: dig_structs.h:147
struct field_info * Vect_default_field_info(struct Map_info *, int, const char *, int)
Get default information about link to database for new dblink.
Definition: field.c:351
int db_copy_table(const char *, const char *, const char *, const char *, const char *, const char *)
Copy a table.
Definition: copy_tab.c:446
#define DB_FAILED
Definition: dbmi.h:72
const char * G_mapset(void)
Get current mapset name.
Definition: gis/mapset.c:33
int Vect_copy(const char *in, const char *mapset, const char *out)
Copy vector map including attribute tables.
Definition: map.c:124
int Vect_copy_tables(const struct Map_info *, struct Map_info *, int)
Copy attribute tables linked to vector map.
void G_zero(void *, int)
Zero out a buffer, buf, of length i.
Definition: gis/zero.c:23
#define GNAME_MAX
Definition: gis.h:177
void G_warning(const char *,...) __attribute__((format(printf
int number
Layer number.
Definition: dig_structs.h:139
Definition: path.h:16
void Vect_destroy_map_struct(struct Map_info *p)
Frees all memory associated with a Map_info structure, including the structure itself.
Definition: map.c:61
#define _(str)
Definition: glocale.h:10
int format
Map format (native, ogr, postgis)
Definition: dig_structs.h:1271
int db_table_exists(const char *, const char *, const char *)
Check if table exists.
#define GV_SIDX_ELEMENT
Native format, spatial index.
Definition: dig_defines.h:22
#define GV_COOR_ELEMENT
Native format, coordinates.
Definition: dig_defines.h:12
int update_cidx
Update category index if vector is modified.
Definition: dig_structs.h:1136
char * getenv()
int G_name_is_fully_qualified(const char *, char *, char *)
Check if map name is fully qualified (map @ mapset)
Definition: nme_in_mps.c:36
void Vect_set_release_support(struct Map_info *Map)
Set spatial index to be realease when vector is closed.
Definition: map.c:548
int G_debug(int, const char *,...) __attribute__((format(printf
#define DB_OK
Definition: dbmi.h:71
char * key
Name of key column (usually &#39;cat&#39;)
Definition: dig_structs.h:159