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
30typedef 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;
37static void globfree (glob_t* pglob);
38static 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 */
62extern 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
93static 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 */
113static 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 */
152static 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
169static 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
210void gvconfig_plugin_install_from_library(GVC_t * gvc, char *path, gvplugin_library_t *library)
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
226static 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"))
235 gvconfig_plugin_install_from_library(gvc, NULL,
236 (gvplugin_library_t *)(s->address));
237}
238
239#ifdef ENABLE_LTDL
240static 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
264char * 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
365static 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, 0, 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 */
479void 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
575static int
576glob (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
613static void
614globfree (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
624