Graphviz  2.41.20170921.2350
gvconfig.c
Go to the documentation of this file.
1 /* $Id$ $Revision$ */
2 /* vim:set shiftwidth=4 ts=8: */
3 
4 /*************************************************************************
5  * Copyright (c) 2011 AT&T Intellectual Property
6  * All rights reserved. This program and the accompanying materials
7  * are made available under the terms of the Eclipse Public License v1.0
8  * which accompanies this distribution, and is available at
9  * http://www.eclipse.org/legal/epl-v10.html
10  *
11  * Contributors: See CVS logs. Details at http://www.graphviz.org/
12  *************************************************************************/
13 
14 #include "config.h"
15 
16 #include "gvconfig.h"
17 
18 #include <string.h>
19 #include <regex.h>
20 
21 #ifdef ENABLE_LTDL
22 #include <sys/types.h>
23 #ifdef _WIN32
24 #include <windows.h>
25 #define GLOB_NOSPACE 1 /* Ran out of memory. */
26 #define GLOB_ABORTED 2 /* Read error. */
27 #define GLOB_NOMATCH 3 /* No matches found. */
28 #define GLOB_NOSORT 4
29 #define DMKEY "Software\\Microsoft" //key to look for library dir
30 typedef struct {
31  int gl_pathc; /* count of total paths so far */
32  int gl_matchc; /* count of paths matching pattern */
33  int gl_offs; /* reserved at beginning of gl_pathv */
34  int gl_flags; /* returned flags */
35  char **gl_pathv; /* list of paths matching pattern */
36 } glob_t;
37 static void globfree (glob_t* pglob);
38 static int glob (GVC_t * gvc, char*, int, int (*errfunc)(const char *, int), glob_t*);
39 #else
40 #include <glob.h>
41 #endif
42 #include <sys/stat.h>
43 #ifdef HAVE_UNISTD_H
44 #include <unistd.h>
45 #endif
46 #endif
47 
48 #ifdef __APPLE__
49 #include <mach-o/dyld.h>
50 #endif
51 
52 #include "memory.h"
53 #include "const.h"
54 #include "types.h"
55 
56 #include "gvplugin.h"
57 #include "gvcjob.h"
58 #include "gvcint.h"
59 #include "gvcproc.h"
60 
61 /* FIXME */
62 extern Dt_t * textfont_dict_open(GVC_t *gvc);
63 
64 /*
65  A config for gvrender is a text file containing a
66  list of plugin librariess and their capabilities using a tcl-like
67  syntax
68 
69  Lines beginning with '#' are ignored as comments
70 
71  Blank lines are allowed and ignored.
72 
73  plugin_library_path packagename {
74  plugin_api {
75  plugin_type plugin_quality
76  ...
77  }
78  ...
79  ...
80 
81  e.g.
82 
83  /usr/lib/graphviz/libgvplugin_cairo.so cairo {renderer {x 0 png 10 ps -10}}
84  /usr/lib/graphviz/libgvplugin_gd.so gd {renderer {png 0 gif 0 jpg 0}}
85 
86  Internally the config is maintained as lists of plugin_types for each plugin_api.
87  If multiple plugins of the same type are found then the highest quality wins.
88  If equal quality then the last-one-installed wins (thus giving preference to
89  external plugins over internal builtins).
90 
91  */
92 
93 static gvplugin_package_t * gvplugin_package_record(GVC_t * gvc, char *path, char *name)
94 {
95  gvplugin_package_t *package = gmalloc(sizeof(gvplugin_package_t));
96  package->path = (path) ? strdup(path) : NULL;
97  package->name = strdup(name);
98  package->next = gvc->packages;
99  gvc->packages = package;
100  return package;
101 }
102 
103 #ifdef ENABLE_LTDL
104 /*
105  separator - consume all non-token characters until next token. This includes:
106  comments: '#' ... '\n'
107  nesting: '{'
108  unnesting: '}'
109  whitespace: ' ','\t','\n'
110 
111  *nest is changed according to nesting/unnesting processed
112  */
113 static void separator(int *nest, char **tokens)
114 {
115  char c, *s;
116 
117  s = *tokens;
118  while ((c = *s)) {
119  /* #->eol = comment */
120  if (c == '#') {
121  s++;
122  while ((c = *s)) {
123  s++;
124  if (c == '\n')
125  break;
126  }
127  continue;
128  }
129  if (c == '{') {
130  (*nest)++;
131  s++;
132  continue;
133  }
134  if (c == '}') {
135  (*nest)--;
136  s++;
137  continue;
138  }
139  if (c == ' ' || c == '\n' || c == '\t') {
140  s++;
141  continue;
142  }
143  break;
144  }
145  *tokens = s;
146 }
147 
148 /*
149  token - capture all characters until next separator, then consume separator,
150  return captured token, leave **tokens pointing to next token.
151  */
152 static char *token(int *nest, char **tokens)
153 {
154  char c, *s, *t;
155 
156  s = t = *tokens;
157  while ((c = *s)) {
158  if (c == '#'
159  || c == ' ' || c == '\t' || c == '\n' || c == '{' || c == '}')
160  break;
161  s++;
162  }
163  *tokens = s;
164  separator(nest, tokens);
165  *s = '\0';
166  return t;
167 }
168 
169 static int gvconfig_plugin_install_from_config(GVC_t * gvc, char *s)
170 {
171  char *path, *name, *api;
172  const char *type;
173  api_t gv_api;
174  int quality, rc;
175  int nest = 0;
176  gvplugin_package_t *package;
177 
178  separator(&nest, &s);
179  while (*s) {
180  path = token(&nest, &s);
181  if (nest == 0)
182  name = token(&nest, &s);
183  else
184  name = "x";
185  package = gvplugin_package_record(gvc, path, name);
186  do {
187  api = token(&nest, &s);
188  gv_api = gvplugin_api(api);
189  do {
190  if (nest == 2) {
191  type = token(&nest, &s);
192  if (nest == 2)
193  quality = atoi(token(&nest, &s));
194  else
195  quality = 0;
196  rc = gvplugin_install (gvc, gv_api,
197  type, quality, package, NULL);
198  if (!rc) {
199  agerr(AGERR, "config error: %s %s %s\n", path, api, type);
200  return 0;
201  }
202  }
203  } while (nest == 2);
204  } while (nest == 1);
205  }
206  return 1;
207 }
208 #endif
209 
211 {
212  gvplugin_api_t *apis;
213  gvplugin_installed_t *types;
214  gvplugin_package_t *package;
215  int i;
216 
217  package = gvplugin_package_record(gvc, path, library->packagename);
218  for (apis = library->apis; (types = apis->types); apis++) {
219  for (i = 0; types[i].type; i++) {
220  gvplugin_install(gvc, apis->api, types[i].type,
221  types[i].quality, package, &types[i]);
222  }
223  }
224 }
225 
226 static void gvconfig_plugin_install_builtins(GVC_t * gvc)
227 {
228  const lt_symlist_t *s;
229  const char *name;
230 
231  if (gvc->common.builtins == NULL) return;
232 
233  for (s = gvc->common.builtins; (name = s->name); s++)
234  if (name[0] == 'g' && strstr(name, "_LTX_library"))
236  (gvplugin_library_t *)(s->address));
237 }
238 
239 #ifdef ENABLE_LTDL
240 static void gvconfig_write_library_config(GVC_t *gvc, char *path, gvplugin_library_t *library, FILE *f)
241 {
242  gvplugin_api_t *apis;
243  gvplugin_installed_t *types;
244  int i;
245 
246  fprintf(f, "%s %s {\n", path, library->packagename);
247  for (apis = library->apis; (types = apis->types); apis++) {
248  fprintf(f, "\t%s {\n", gvplugin_api_name(apis->api));
249  for (i = 0; types[i].type; i++) {
250  /* verify that dependencies are available */
251  if (! (gvplugin_load(gvc, apis->api, types[i].type)))
252  fprintf(f, "#FAILS");
253  fprintf(f, "\t\t%s %d\n", types[i].type, types[i].quality);
254  }
255  fputs ("\t}\n", f);
256  }
257  fputs ("}\n", f);
258 }
259 
260 #define BSZ 1024
261 #define DOTLIBS "/.libs"
262 #define STRLEN(s) (sizeof(s)-1)
263 
264 char * gvconfig_libdir(GVC_t * gvc)
265 {
266  static char line[BSZ];
267  static char *libdir;
268  static boolean dirShown = 0;
269  char *tmp;
270 
271  if (!libdir) {
272  libdir=getenv("GVBINDIR");
273  if (!libdir) {
274 #ifdef _WIN32
275  int r;
276  char* s;
277 
278  MEMORY_BASIC_INFORMATION mbi;
279  if (VirtualQuery (&gvconfig_libdir, &mbi, sizeof(mbi)) == 0) {
280  agerr(AGERR,"failed to get handle for executable.\n");
281  return 0;
282  }
283  r = GetModuleFileName ((HMODULE)mbi.AllocationBase, line, BSZ);
284  if (!r || (r == BSZ)) {
285  agerr(AGERR,"failed to get path for executable.\n");
286  return 0;
287  }
288  s = strrchr(line,'\\');
289  if (!s) {
290  agerr(AGERR,"no slash in path %s.\n", line);
291  return 0;
292  }
293  *s = '\0';
294  libdir = line;
295 #else
296  libdir = GVLIBDIR;
297 #ifdef __APPLE__
298  uint32_t i, c = _dyld_image_count();
299  size_t len, ind;
300  const char* path;
301  for (i = 0; i < c; ++i) {
302  path = _dyld_get_image_name(i);
303  tmp = strstr(path, "/libgvc.");
304  if (tmp) {
305  if (tmp > path) {
306  /* Check for real /lib dir. Don't accept pre-install /.libs */
307  char* s = tmp-1;
308  /* back up to previous slash (or head of string) */
309  while ((*s != '/') && (s > path)) s--;
310  if (strncmp (s, DOTLIBS, STRLEN(DOTLIBS)) == 0)
311  continue;
312  }
313 
314  ind = tmp - path; /* byte offset */
315  len = ind + sizeof("/graphviz");
316  if (len < BSZ)
317  libdir = line;
318  else
319  libdir = gmalloc(len);
320  bcopy (path, libdir, ind);
321  /* plugins are in "graphviz" subdirectory */
322  strcpy(libdir+ind, "/graphviz");
323  break;
324  }
325  }
326 #else
327  FILE* f = fopen ("/proc/self/maps", "r");
328  char* path;
329  if (f) {
330  while (!feof (f)) {
331  if (!fgets (line, sizeof (line), f))
332  continue;
333  if (!strstr (line, " r-xp "))
334  continue;
335  path = strchr (line, '/');
336  if (!path)
337  continue;
338  tmp = strstr (path, "/libgvc.");
339  if (tmp) {
340  *tmp = 0;
341  /* Check for real /lib dir. Don't accept pre-install /.libs */
342  if (strcmp(strrchr(path,'/'), "/.libs") == 0)
343  continue;
344  strcpy(line, path); /* use line buffer for result */
345  strcat(line, "/graphviz"); /* plugins are in "graphviz" subdirectory */
346  libdir = line;
347  break;
348  }
349  }
350  fclose (f);
351  }
352 #endif
353 #endif
354  }
355  }
356  if (gvc->common.verbose && !dirShown) {
357  fprintf (stderr, "libdir = \"%s\"\n", (libdir ? libdir : "<null>"));
358  dirShown = 1;
359  }
360  return libdir;
361 }
362 #endif
363 
364 #ifdef ENABLE_LTDL
365 static void config_rescan(GVC_t *gvc, char *config_path)
366 {
367  FILE *f = NULL;
368  glob_t globbuf;
369  char *config_glob, *config_re, *path, *libdir;
370  int i, rc, re_status;
371  gvplugin_library_t *library;
372  regex_t re;
373 #ifndef _WIN32
374  char *plugin_glob = "libgvplugin_*";
375 #endif
376 #if defined(DARWIN_DYLIB)
377  char *plugin_re_beg = "[^0-9]\\.";
378  char *plugin_re_end = "\\.dylib$";
379 #elif defined(__MINGW32__)
380  char *plugin_glob = "libgvplugin_*";
381  char *plugin_re_beg = "[^0-9]-";
382  char *plugin_re_end = "\\.dll$";
383 #elif defined(__CYGWIN__)
384  plugin_glob = "cyggvplugin_*";
385  char *plugin_re_beg = "[^0-9]-";
386  char *plugin_re_end = "\\.dll$";
387 #elif defined(_WIN32)
388  char *plugin_glob = "gvplugin_*";
389  char *plugin_re_beg = "[^0-9]";
390  char *plugin_re_end = "\\.dll$";
391 #elif ((defined(__hpux__) || defined(__hpux)) && !(defined(__ia64)))
392  char *plugin_re_beg = "\\.sl\\.";
393  char *plugin_re_end = "$";
394 #else
395  /* Everyone else */
396  char *plugin_re_beg = "\\.so\\.";
397  char *plugin_re_end= "$";
398 #endif
399 
400  if (config_path) {
401  f = fopen(config_path,"w");
402  if (!f) {
403  agerr(AGERR,"failed to open %s for write.\n", config_path);
404  exit(1);
405  }
406 
407  fprintf(f, "# This file was generated by \"dot -c\" at time of install.\n\n");
408  fprintf(f, "# You may temporarily disable a plugin by removing or commenting out\n");
409  fprintf(f, "# a line in this file, or you can modify its \"quality\" value to affect\n");
410  fprintf(f, "# default plugin selection.\n\n");
411  fprintf(f, "# Manual edits to this file **will be lost** on upgrade.\n\n");
412  }
413 
414  libdir = gvconfig_libdir(gvc);
415 
416  config_re = gmalloc(strlen(plugin_re_beg) + 20 + strlen(plugin_re_end) + 1);
417 
418 #if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
419  sprintf(config_re,"%s%s", plugin_re_beg, plugin_re_end);
420 #elif defined(GVPLUGIN_VERSION)
421  sprintf(config_re,"%s%d%s", plugin_re_beg, GVPLUGIN_VERSION, plugin_re_end);
422 #else
423  sprintf(config_re,"%s[0-9]+%s", plugin_re_beg, plugin_re_end);
424 #endif
425 
426  if (regcomp(&re, config_re, REG_EXTENDED|REG_NOSUB) != 0) {
427  agerr(AGERR,"cannot compile regular expression %s", config_re);
428  }
429 
430  config_glob = gmalloc(strlen(libdir) + 1 + strlen(plugin_glob) + 1);
431  strcpy(config_glob, libdir);
432  strcat(config_glob, DIRSEP);
433  strcat(config_glob, plugin_glob);
434 
435  /* load all libraries even if can't save config */
436 
437 #if defined(_WIN32)
438  rc = glob(gvc, config_glob, GLOB_NOSORT, NULL, &globbuf);
439 #else
440  rc = glob(config_glob, GLOB_NOSORT, NULL, &globbuf);
441 #endif
442  if (rc == 0) {
443  for (i = 0; i < globbuf.gl_pathc; i++) {
444  re_status = regexec(&re, globbuf.gl_pathv[i], (size_t) 0, NULL, 0);
445  if (re_status == 0) {
446  library = gvplugin_library_load(gvc, globbuf.gl_pathv[i]);
447  if (library) {
448  gvconfig_plugin_install_from_library(gvc, globbuf.gl_pathv[i], library);
449  }
450  }
451  }
452  /* rescan with all libs loaded to check cross dependencies */
453  for (i = 0; i < globbuf.gl_pathc; i++) {
454  re_status = regexec(&re, globbuf.gl_pathv[i], (size_t) 0, NULL, 0);
455  if (re_status == 0) {
456  library = gvplugin_library_load(gvc, globbuf.gl_pathv[i]);
457  if (library) {
458  path = strrchr(globbuf.gl_pathv[i],DIRSEP[0]);
459  if (path)
460  path++;
461  if (f && path)
462  gvconfig_write_library_config(gvc, path, library, f);
463  }
464  }
465  }
466  }
467  regfree(&re);
468  globfree(&globbuf);
469  free(config_glob);
470  free(config_re);
471  if (f)
472  fclose(f);
473 }
474 #endif
475 
476 /*
477  gvconfig - parse a config file and install the identified plugins
478  */
479 void gvconfig(GVC_t * gvc, boolean rescan)
480 {
481 #if 0
482  gvplugin_library_t **libraryp;
483 #endif
484 #ifdef ENABLE_LTDL
485  int sz, rc;
486  struct stat config_st, libdir_st;
487  FILE *f = NULL;
488  char *config_text = NULL;
489  char *libdir;
490  char *config_file_name = GVPLUGIN_CONFIG_FILE;
491 
492 #define MAX_SZ_CONFIG 100000
493 #endif
494 
495  /* builtins don't require LTDL */
496  gvconfig_plugin_install_builtins(gvc);
497 
498  gvc->config_found = FALSE;
499 #ifdef ENABLE_LTDL
500  if (gvc->common.demand_loading) {
501  /* see if there are any new plugins */
502  libdir = gvconfig_libdir(gvc);
503  rc = stat(libdir, &libdir_st);
504  if (rc == -1) {
505  gvtextlayout_select(gvc); /* choose best available textlayout plugin immediately */
506  /* if we fail to stat it then it probably doesn't exist so just fail silently */
507  return;
508  }
509 
510  if (! gvc->config_path) {
511  gvc->config_path = gmalloc(strlen(libdir) + 1 + strlen(config_file_name) + 1);
512  strcpy(gvc->config_path, libdir);
513  strcat(gvc->config_path, DIRSEP);
514  strcat(gvc->config_path, config_file_name);
515  }
516 
517  if (rescan) {
518  config_rescan(gvc, gvc->config_path);
519  gvc->config_found = TRUE;
520  gvtextlayout_select(gvc); /* choose best available textlayout plugin immediately */
521  return;
522  }
523 
524  /* load in the cached plugin library data */
525 
526  rc = stat(gvc->config_path, &config_st);
527  if (rc == -1) {
528  gvtextlayout_select(gvc); /* choose best available textlayout plugin immediately */
529  /* silently return without setting gvc->config_found = TRUE */
530  return;
531  }
532  else if (config_st.st_size > MAX_SZ_CONFIG) {
533  agerr(AGERR,"%s is bigger than I can handle.\n", gvc->config_path);
534  }
535  else {
536  f = fopen(gvc->config_path,"r");
537  if (!f) {
538  agerr (AGERR,"failed to open %s for read.\n", gvc->config_path);
539  return;
540  }
541  else {
542  config_text = gmalloc(config_st.st_size + 1);
543  sz = fread(config_text, 1, config_st.st_size, f);
544  if (sz == 0) {
545  agerr(AGERR,"%s is zero sized, or other read error.\n", gvc->config_path);
546  free(config_text);
547  }
548  else {
549  gvc->config_found = TRUE;
550  config_text[sz] = '\0'; /* make input into a null terminated string */
551  rc = gvconfig_plugin_install_from_config(gvc, config_text);
552  /* NB. config_text not freed because we retain char* into it */
553  }
554  }
555  if (f) {
556  fclose(f);
557  }
558  }
559  }
560 #endif
561  gvtextlayout_select(gvc); /* choose best available textlayout plugin immediately */
562  textfont_dict_open(gvc); /* initialize font dict */
563 }
564 
565 #ifdef ENABLE_LTDL
566 #ifdef _WIN32
567 
568 /* Emulating windows glob */
569 
570 /* glob:
571  * Assumes only GLOB_NOSORT flag given. That is, there is no offset,
572  * and no previous call to glob.
573  */
574 
575 static int
576 glob (GVC_t* gvc, char* pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob)
577 {
578  char* libdir;
579  WIN32_FIND_DATA wfd;
580  HANDLE h;
581  char** str=0;
582  int arrsize=0;
583  int cnt = 0;
584 
585  pglob->gl_pathc = 0;
586  pglob->gl_pathv = NULL;
587 
588  h = FindFirstFile (pattern, &wfd);
589  if (h == INVALID_HANDLE_VALUE) return GLOB_NOMATCH;
590  libdir = gvconfig_libdir(gvc);
591  do {
592  if (cnt >= arrsize-1) {
593  arrsize += 512;
594  if (str) str = (char**)realloc (str, arrsize*sizeof(char*));
595  else str = (char**)malloc (arrsize*sizeof(char*));
596  if (!str) return GLOB_NOSPACE;
597  }
598  str[cnt] = (char*)malloc (strlen(libdir)+1+strlen(wfd.cFileName)+1);
599  if (!str[cnt]) return GLOB_NOSPACE;
600  strcpy(str[cnt],libdir);
601  strcat(str[cnt],DIRSEP);
602  strcat(str[cnt],wfd.cFileName);
603  cnt++;
604  } while (FindNextFile (h, &wfd));
605  str[cnt] = 0;
606 
607  pglob->gl_pathc = cnt;
608  pglob->gl_pathv = (char**)realloc(str, (cnt+1)*sizeof(char*));
609 
610  return 0;
611 }
612 
613 static void
614 globfree (glob_t* pglob)
615 {
616  int i;
617  for (i = 0; i < pglob->gl_pathc; i++)
618  free (pglob->gl_pathv[i]);
619  if (pglob->gl_pathv)
620  free (pglob->gl_pathv);
621 }
622 #endif
623 #endif
#define BSZ
Definition: layout.c:309
char * gvconfig_libdir(GVC_t *gvc)
Definition: cgraph.h:388
#define DIRSEP
Definition: gvcint.h:152
void gvconfig(GVC_t *gvc, boolean rescan)
Definition: gvconfig.c:479
Dt_t * textfont_dict_open(GVC_t *gvc)
Definition: textspan.c:279
char * packagename
Definition: gvplugin.h:55
const char * name
Definition: gvcext.h:60
int demand_loading
Definition: gvcommon.h:34
void * gmalloc(size_t nbytes)
Definition: memory.c:42
api_t
Definition: gvcext.h:34
int agerr(agerrlevel_t level, const char *fmt,...)
Definition: agerror.c:141
const char * type
Definition: gvplugin.h:39
gvplugin_available_t * gvplugin_load(GVC_t *gvc, api_t api, const char *type)
Definition: gvplugin.c:262
const lt_symlist_t * builtins
Definition: gvcommon.h:33
char * gvplugin_api_name(api_t api)
Definition: gvplugin.c:71
struct path path
gvplugin_api_t * apis
Definition: gvplugin.h:57
gvplugin_package_t * packages
Definition: gvcint.h:89
Definition: gvcint.h:70
void gvconfig_plugin_install_from_library(GVC_t *gvc, char *path, gvplugin_library_t *library)
Definition: gvconfig.c:210
#define re
Definition: edges.h:33
Definition: grammar.c:79
boolean gvplugin_install(GVC_t *gvc, api_t api, const char *typestr, int quality, gvplugin_package_t *package, gvplugin_installed_t *typeptr)
Definition: gvplugin.c:83
int verbose
Definition: gvcommon.h:24
int gvtextlayout_select(GVC_t *gvc)
Definition: gvtextlayout.c:25
#define NULL
Definition: logic.h:39
#define STRLEN(s)
Definition: adjust.c:978
GVC_t * gvc
Definition: htmlparse.c:87
boolean config_found
Definition: gvcint.h:74
Definition: types.h:100
GVCOMMON_t common
Definition: gvcint.h:71
Definition: cdt.h:99
agxbuf * str
Definition: htmlparse.c:85
void * address
Definition: gvcext.h:61
gvplugin_installed_t * types
Definition: gvplugin.h:51
gvplugin_library_t * gvplugin_library_load(GVC_t *gvc, char *path)
Definition: gvplugin.c:158
char * config_path
Definition: gvcint.h:73
api_t gvplugin_api(char *str)
Definition: gvplugin.c:59
#define FALSE
Definition: cgraph.h:35
#define TRUE
Definition: cgraph.h:38