Graphviz  2.29.20120524.0446
lib/gvc/gvconfig.c
Go to the documentation of this file.
00001 /* $Id$ $Revision$ */
00002 /* vim:set shiftwidth=4 ts=8: */
00003 
00004 /*************************************************************************
00005  * Copyright (c) 2011 AT&T Intellectual Property 
00006  * All rights reserved. This program and the accompanying materials
00007  * are made available under the terms of the Eclipse Public License v1.0
00008  * which accompanies this distribution, and is available at
00009  * http://www.eclipse.org/legal/epl-v10.html
00010  *
00011  * Contributors: See CVS logs. Details at http://www.graphviz.org/
00012  *************************************************************************/
00013 
00014 #ifdef HAVE_CONFIG_H
00015 #include "config.h"
00016 #endif
00017 
00018 #include "gvconfig.h"
00019 
00020 #include        <string.h>
00021 
00022 #ifdef ENABLE_LTDL
00023 #include        <sys/types.h>
00024 #ifdef WIN32
00025 #include <windows.h>
00026 #define GLOB_NOSPACE    1   /* Ran out of memory.  */
00027 #define GLOB_ABORTED    2   /* Read error.  */
00028 #define GLOB_NOMATCH    3   /* No matches found.  */
00029 #define GLOB_NOSORT     4
00030 #define DMKEY "Software\\Microsoft" //key to look for library dir
00031 #include "regex_win32.h"
00032 //#include        <regex_win32.c>
00033 typedef struct {
00034     int gl_pathc;           /* count of total paths so far */
00035     int gl_matchc;          /* count of paths matching pattern */
00036     int gl_offs;            /* reserved at beginning of gl_pathv */
00037     int gl_flags;           /* returned flags */
00038     char **gl_pathv;        /* list of paths matching pattern */
00039 } glob_t;
00040 static void globfree (glob_t* pglob);
00041 static int glob (GVC_t * gvc, char*, int, int (*errfunc)(const char *, int), glob_t*);
00042 #else
00043 #include        <regex.h>
00044 #include        <glob.h>
00045 #endif 
00046 #include        <sys/stat.h>
00047 #ifdef HAVE_UNISTD_H
00048 #include        <unistd.h>
00049 #endif
00050 #endif
00051 
00052 #ifdef __APPLE__
00053 #include <mach-o/dyld.h>
00054 #endif
00055 
00056 #include        "memory.h"
00057 #include        "const.h"
00058 #include        "types.h"
00059 
00060 #include        "gvplugin.h"
00061 #include        "gvcjob.h"
00062 #include        "gvcint.h"
00063 #include        "gvcproc.h"
00064 
00065 /*
00066     A config for gvrender is a text file containing a
00067     list of plugin librariess and their capabilities using a tcl-like
00068     syntax
00069 
00070     Lines beginning with '#' are ignored as comments
00071 
00072     Blank lines are allowed and ignored.
00073 
00074     plugin_library_path packagename {
00075         plugin_api {
00076             plugin_type plugin_quality
00077             ...
00078         }
00079         ...
00080     ...
00081 
00082     e.g.
00083 
00084         /usr/lib/graphviz/libgvplugin_cairo.so cairo {renderer {x 0 png 10 ps -10}}
00085         /usr/lib/graphviz/libgvplugin_gd.so gd {renderer {png 0 gif 0 jpg 0}}
00086 
00087     Internally the config is maintained as lists of plugin_types for each plugin_api.
00088     If multiple plugins of the same type are found then the highest quality wins.
00089     If equal quality then the last-one-installed wins (thus giving preference to
00090     external plugins over internal builtins).
00091 
00092  */
00093 
00094 static gvplugin_package_t * gvplugin_package_record(GVC_t * gvc, char *path, char *name)
00095 {
00096     gvplugin_package_t *package = gmalloc(sizeof(gvplugin_package_t));
00097     package->path = (path) ? strdup(path) : NULL;
00098     package->name = strdup(name);
00099     package->next = gvc->packages;
00100     gvc->packages = package;
00101     return package;
00102 }
00103 
00104 #ifdef ENABLE_LTDL
00105 /*
00106   separator - consume all non-token characters until next token.  This includes:
00107         comments:   '#' ... '\n'
00108         nesting:    '{'
00109         unnesting:  '}'
00110         whitespace: ' ','\t','\n'
00111 
00112         *nest is changed according to nesting/unnesting processed
00113  */
00114 static void separator(int *nest, char **tokens)
00115 {
00116     char c, *s;
00117 
00118     s = *tokens;
00119     while ((c = *s)) {
00120         /* #->eol = comment */
00121         if (c == '#') {
00122             s++;
00123             while ((c = *s)) {
00124                 s++;
00125                 if (c == '\n')
00126                     break;
00127             }
00128             continue;
00129         }
00130         if (c == '{') {
00131             (*nest)++;
00132             s++;
00133             continue;
00134         }
00135         if (c == '}') {
00136             (*nest)--;
00137             s++;
00138             continue;
00139         }
00140         if (c == ' ' || c == '\n' || c == '\t') {
00141             s++;
00142             continue;
00143         }
00144         break;
00145     }
00146     *tokens = s;
00147 }
00148 
00149 /* 
00150   token - capture all characters until next separator, then consume separator,
00151         return captured token, leave **tokens pointing to next token.
00152  */
00153 static char *token(int *nest, char **tokens)
00154 {
00155     char c, *s, *t;
00156 
00157     s = t = *tokens;
00158     while ((c = *s)) {
00159         if (c == '#'
00160             || c == ' ' || c == '\t' || c == '\n' || c == '{' || c == '}')
00161             break;
00162         s++;
00163     }
00164     *tokens = s;
00165     separator(nest, tokens);
00166     *s = '\0';
00167     return t;
00168 }
00169 
00170 static int gvconfig_plugin_install_from_config(GVC_t * gvc, char *s)
00171 {
00172     char *path, *name, *api;
00173     const char *type;
00174     api_t gv_api;
00175     int quality, rc;
00176     int nest = 0;
00177     gvplugin_package_t *package;
00178 
00179     separator(&nest, &s);
00180     while (*s) {
00181         path = token(&nest, &s);
00182         if (nest == 0)
00183             name = token(&nest, &s);
00184         else
00185             name = "x";
00186         package = gvplugin_package_record(gvc, path, name);
00187         do {
00188             api = token(&nest, &s);
00189             gv_api = gvplugin_api(api);
00190             if (gv_api == -1) {
00191                 agerr(AGERR, "invalid api in config: %s %s\n", path, api);
00192                 return 0;
00193             }
00194             do {
00195                 if (nest == 2) {
00196                     type = token(&nest, &s);
00197                     if (nest == 2)
00198                         quality = atoi(token(&nest, &s));
00199                     else
00200                         quality = 0;
00201                     rc = gvplugin_install (gvc, gv_api,
00202                                     type, quality, package, NULL);
00203                     if (!rc) {
00204                         agerr(AGERR, "config error: %s %s %s\n", path, api, type);
00205                         return 0;
00206                     }
00207                 }
00208             } while (nest == 2);
00209         } while (nest == 1);
00210     }
00211     return 1;
00212 }
00213 #endif
00214 
00215 void gvconfig_plugin_install_from_library(GVC_t * gvc, char *path, gvplugin_library_t *library)
00216 {
00217     gvplugin_api_t *apis;
00218     gvplugin_installed_t *types;
00219     gvplugin_package_t *package;
00220     int i;
00221 
00222     package = gvplugin_package_record(gvc, path, library->packagename);
00223     for (apis = library->apis; (types = apis->types); apis++) {
00224         for (i = 0; types[i].type; i++) {
00225             gvplugin_install(gvc, apis->api, types[i].type,
00226                         types[i].quality, package, &types[i]);
00227         }
00228     }
00229 }
00230 
00231 static void gvconfig_plugin_install_builtins(GVC_t * gvc)
00232 {
00233     const lt_symlist_t *s;
00234     const char *name;
00235 
00236     if (gvc->common.builtins == NULL) return;
00237 
00238     for (s = gvc->common.builtins; (name = s->name); s++)
00239         if (name[0] == 'g' && strstr(name, "_LTX_library")) 
00240             gvconfig_plugin_install_from_library(gvc, NULL,
00241                     (gvplugin_library_t *)(s->address));
00242 }
00243 
00244 #ifdef ENABLE_LTDL
00245 static void gvconfig_write_library_config(GVC_t *gvc, char *path, gvplugin_library_t *library, FILE *f)
00246 {
00247     gvplugin_api_t *apis;
00248     gvplugin_installed_t *types;
00249     int i;
00250 
00251     fprintf(f, "%s %s {\n", path, library->packagename);
00252     for (apis = library->apis; (types = apis->types); apis++) {
00253         fprintf(f, "\t%s {\n", gvplugin_api_name(apis->api));
00254         for (i = 0; types[i].type; i++) {
00255             /* verify that dependencies are available */
00256             if (! (gvplugin_load(gvc, apis->api, types[i].type)))
00257                 fprintf(f, "#FAILS");
00258             fprintf(f, "\t\t%s %d\n", types[i].type, types[i].quality);
00259         }
00260         fputs ("\t}\n", f);
00261     }
00262     fputs ("}\n", f);
00263 }
00264 
00265 #define BSZ 1024
00266 #define DOTLIBS "/.libs"
00267 #define STRLEN(s) (sizeof(s)-1)
00268 
00269 char * gvconfig_libdir(GVC_t * gvc)
00270 {
00271     static char line[BSZ];
00272     static char *libdir;
00273     static boolean dirShown = 0; 
00274     char *tmp;
00275 
00276     if (!libdir) {
00277         libdir=getenv("GVBINDIR");
00278         if (!libdir) {
00279 #ifdef WIN32
00280             int r;
00281             char* s;
00282                 
00283                 MEMORY_BASIC_INFORMATION mbi;
00284                 if (VirtualQuery (&gvconfig_libdir, &mbi, sizeof(mbi)) == 0) {
00285                 agerr(AGERR,"failed to get handle for executable.\n");
00286                 return 0;
00287             }
00288             r = GetModuleFileName ((HMODULE)mbi.AllocationBase, line, BSZ);
00289             if (!r || (r == BSZ)) {
00290                 agerr(AGERR,"failed to get path for executable.\n");
00291                 return 0;
00292             }
00293             s = strrchr(line,'\\');
00294             if (!s) {
00295                 agerr(AGERR,"no slash in path %s.\n", line);
00296                 return 0;
00297             }
00298             *s = '\0';
00299             libdir = line;
00300 #else
00301             libdir = GVLIBDIR;      
00302 #ifdef __APPLE__
00303             uint32_t i, c = _dyld_image_count();
00304             size_t len, ind;
00305             const char* path;
00306             for (i = 0; i < c; ++i) {
00307                 path = _dyld_get_image_name(i);
00308                 tmp = strstr(path, "/libgvc.");
00309                 if (tmp) {
00310                     if (tmp > path) {
00311                         /* Check for real /lib dir. Don't accept pre-install /.libs */
00312                         char* s = tmp-1;
00313                         /* back up to previous slash (or head of string) */
00314                         while ((*s != '/') && (s > path)) s--;
00315                         if (strncmp (s, DOTLIBS, STRLEN(DOTLIBS)) == 0);
00316                             continue;
00317                     }
00318 
00319                     ind = tmp - path;  /* byte offset */
00320                     len = ind + sizeof("/graphviz");
00321                     if (len < BSZ)
00322                         libdir = line;
00323                     else
00324                         libdir = gmalloc(len);
00325                     bcopy (path, libdir, ind);
00326                     /* plugins are in "graphviz" subdirectory */
00327                     strcpy(libdir+ind, "/graphviz");  
00328                     break;
00329                 }
00330             }
00331 #else
00332             FILE* f = fopen ("/proc/self/maps", "r");
00333             char* path;
00334             if (f) {
00335                 while (!feof (f)) {
00336                     if (!fgets (line, sizeof (line), f))
00337                         continue;
00338                     if (!strstr (line, " r-xp "))
00339                         continue;
00340                     path = strchr (line, '/');
00341                     if (!path)
00342                         continue;
00343                     tmp = strstr (path, "/libgvc.");
00344                     if (tmp) {
00345                         *tmp = 0;
00346                         /* Check for real /lib dir. Don't accept pre-install /.libs */
00347                         if (strcmp(strrchr(path,'/'), "/.libs") == 0)
00348                             continue;
00349                         strcpy(line, path);  /* use line buffer for result */
00350                         strcat(line, "/graphviz");  /* plugins are in "graphviz" subdirectory */
00351                         libdir = line;
00352                         break;
00353                     }
00354                 }
00355                 fclose (f);
00356             }
00357 #endif
00358 #endif
00359         }
00360     }
00361     if (gvc->common.verbose && !dirShown) {
00362         fprintf (stderr, "libdir = \"%s\"\n", (libdir ? libdir : "<null>"));
00363         dirShown = 1;
00364     }
00365     return libdir;
00366 }
00367 #endif
00368 
00369 #ifdef ENABLE_LTDL
00370 static void config_rescan(GVC_t *gvc, char *config_path)
00371 {
00372     FILE *f = NULL;
00373     glob_t globbuf;
00374     char *config_glob, *config_re, *path, *libdir;
00375     int i, rc, re_status;
00376     gvplugin_library_t *library;
00377     regex_t re;
00378 #ifndef WIN32
00379     char *plugin_glob = "libgvplugin_*";
00380 #endif
00381 #if defined(DARWIN_DYLIB)
00382     char *plugin_re_beg = "[^0-9]\\.";
00383     char *plugin_re_end = "\\.dylib$";
00384 #elif defined(__MINGW32__)
00385         char *plugin_glob = "libgvplugin_*";
00386         char *plugin_re_beg = "[^0-9]-";
00387     char *plugin_re_end = "\\.dll$"; 
00388 #elif defined(__CYGWIN__)
00389     plugin_glob = "cyggvplugin_*";
00390     char *plugin_re_beg = "[^0-9]-";
00391     char *plugin_re_end = "\\.dll$"; 
00392 #elif defined(WIN32)
00393     char *plugin_glob = "gvplugin_*";
00394     char *plugin_re_beg = "[^0-9]";
00395     char *plugin_re_end = "\\.dll$"; 
00396 #elif ((defined(__hpux__) || defined(__hpux)) && !(defined(__ia64)))
00397     char *plugin_re_beg = "\\.sl\\.";
00398     char *plugin_re_end = "$"; 
00399 #else
00400     /* Everyone else */
00401     char *plugin_re_beg = "\\.so\\.";
00402     char *plugin_re_end= "$";
00403 #endif
00404 
00405     if (config_path) {
00406         f = fopen(config_path,"w");
00407         if (!f) {
00408             agerr(AGERR,"failed to open %s for write.\n", config_path);
00409             exit(1);
00410         }
00411 
00412         fprintf(f, "# This file was generated by \"dot -c\" at time of install.\n\n");
00413         fprintf(f, "# You may temporarily disable a plugin by removing or commenting out\n");
00414         fprintf(f, "# a line in this file, or you can modify its \"quality\" value to affect\n");
00415         fprintf(f, "# default plugin selection.\n\n");
00416         fprintf(f, "# Manual edits to this file **will be lost** on upgrade.\n\n");
00417     }
00418 
00419     libdir = gvconfig_libdir(gvc);
00420 
00421     config_re = gmalloc(strlen(plugin_re_beg) + 20 + strlen(plugin_re_end) + 1);
00422 
00423 #if defined(WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
00424     sprintf(config_re,"%s%s", plugin_re_beg, plugin_re_end);
00425 #elif defined(GVPLUGIN_VERSION)
00426     sprintf(config_re,"%s%d%s", plugin_re_beg, GVPLUGIN_VERSION, plugin_re_end);
00427 #else
00428     sprintf(config_re,"%s[0-9]+%s", plugin_re_beg, plugin_re_end);
00429 #endif
00430 
00431     if (regcomp(&re, config_re, REG_EXTENDED|REG_NOSUB) != 0) {
00432         agerr(AGERR,"cannot compile regular expression %s", config_re);
00433     }
00434 
00435     config_glob = gmalloc(strlen(libdir) + 1 + strlen(plugin_glob) + 1);
00436     strcpy(config_glob, libdir);
00437         strcat(config_glob, DIRSEP);
00438     strcat(config_glob, plugin_glob);
00439 
00440     /* load all libraries even if can't save config */
00441 
00442 #if defined(WIN32)
00443     rc = glob(gvc, config_glob, GLOB_NOSORT, NULL, &globbuf);
00444 #else
00445     rc = glob(config_glob, GLOB_NOSORT, NULL, &globbuf);
00446 #endif
00447     if (rc == 0) {
00448         for (i = 0; i < globbuf.gl_pathc; i++) {
00449             re_status = regexec(&re, globbuf.gl_pathv[i], (size_t) 0, NULL, 0);
00450             if (re_status == 0) {
00451                 library = gvplugin_library_load(gvc, globbuf.gl_pathv[i]);
00452                 if (library) {
00453                     gvconfig_plugin_install_from_library(gvc, globbuf.gl_pathv[i], library);
00454                 }
00455             }
00456         }
00457         /* rescan with all libs loaded to check cross dependencies */
00458         for (i = 0; i < globbuf.gl_pathc; i++) {
00459             re_status = regexec(&re, globbuf.gl_pathv[i], (size_t) 0, NULL, 0);
00460             if (re_status == 0) {
00461                 library = gvplugin_library_load(gvc, globbuf.gl_pathv[i]);
00462                 if (library) {
00463                     path = strrchr(globbuf.gl_pathv[i],DIRSEP[0]);
00464                     if (path)
00465                         path++;
00466                     if (f && path)
00467                         gvconfig_write_library_config(gvc, path, library, f);
00468                 }
00469             }
00470         }
00471     }
00472     regfree(&re);
00473     globfree(&globbuf);
00474     free(config_glob);
00475     free(config_re);
00476     if (f)
00477         fclose(f);
00478 }
00479 #endif
00480 
00481 /*
00482   gvconfig - parse a config file and install the identified plugins
00483  */
00484 void gvconfig(GVC_t * gvc, boolean rescan)
00485 {
00486 #if 0
00487     gvplugin_library_t **libraryp;
00488 #endif
00489 #ifdef ENABLE_LTDL
00490     int sz, rc;
00491     struct stat config_st, libdir_st;
00492     FILE *f = NULL;
00493     char *config_text = NULL;
00494     char *libdir;
00495     char *config_file_name = GVPLUGIN_CONFIG_FILE;
00496 
00497 #define MAX_SZ_CONFIG 100000
00498 #endif
00499     
00500     /* builtins don't require LTDL */
00501     gvconfig_plugin_install_builtins(gvc);
00502    
00503     gvc->config_found = FALSE;
00504 #ifdef ENABLE_LTDL
00505     if (gvc->common.demand_loading) {
00506         /* see if there are any new plugins */
00507         libdir = gvconfig_libdir(gvc);
00508         rc = stat(libdir, &libdir_st);
00509         if (rc == -1) {
00510             /* if we fail to stat it then it probably doesn't exist so just fail silently */
00511             return;
00512         }
00513     
00514         if (! gvc->config_path) {
00515             gvc->config_path = gmalloc(strlen(libdir) + 1 + strlen(config_file_name) + 1);
00516             strcpy(gvc->config_path, libdir);
00517             strcat(gvc->config_path, DIRSEP);
00518             strcat(gvc->config_path, config_file_name);
00519         }
00520         
00521         if (rescan) {
00522             config_rescan(gvc, gvc->config_path);
00523             gvc->config_found = TRUE;
00524             return;
00525         }
00526     
00527         /* load in the cached plugin library data */
00528     
00529         rc = stat(gvc->config_path, &config_st);
00530         if (rc == -1) {
00531             /* silently return without setting gvc->config_found = TRUE */
00532             return;
00533         }
00534         else if (config_st.st_size > MAX_SZ_CONFIG) {
00535             agerr(AGERR,"%s is bigger than I can handle.\n", gvc->config_path);
00536         }
00537         else {
00538             f = fopen(gvc->config_path,"r");
00539             if (!f) {
00540                 agerr (AGERR,"failed to open %s for read.\n", gvc->config_path);
00541                 return;
00542             }
00543             else {
00544                 config_text = gmalloc(config_st.st_size + 1);
00545                 sz = fread(config_text, 1, config_st.st_size, f);
00546                 if (sz == 0) {
00547                     agerr(AGERR,"%s is zero sized, or other read error.\n", gvc->config_path);
00548                     free(config_text);
00549                 }
00550                 else {
00551                     gvc->config_found = TRUE;
00552                     config_text[sz] = '\0';  /* make input into a null terminated string */
00553                     rc = gvconfig_plugin_install_from_config(gvc, config_text);
00554                     /* NB. config_text not freed because we retain char* into it */
00555                 }
00556             }
00557             if (f) {
00558                 fclose(f);
00559             }
00560         }
00561     }
00562 #endif
00563     gvtextlayout_select(gvc);   /* choose best available textlayout plugin immediately */
00564 }
00565 
00566 #ifdef ENABLE_LTDL
00567 #ifdef WIN32
00568 
00569 /* Emulating windows glob */
00570 
00571 /* glob:
00572  * Assumes only GLOB_NOSORT flag given. That is, there is no offset,
00573  * and no previous call to glob.
00574  */
00575 
00576 static int
00577 glob (GVC_t* gvc, char* pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob)
00578 {
00579     char* libdir;
00580     WIN32_FIND_DATA wfd;
00581     HANDLE h;
00582     char** str=0;
00583     int arrsize=0;
00584     int cnt = 0;
00585     
00586     pglob->gl_pathc = 0;
00587     pglob->gl_pathv = NULL;
00588     
00589     h = FindFirstFile (pattern, &wfd);
00590     if (h == INVALID_HANDLE_VALUE) return GLOB_NOMATCH;
00591     libdir = gvconfig_libdir(gvc);
00592     do {
00593       if (cnt >= arrsize-1) {
00594         arrsize += 512;
00595         if (str) str = (char**)realloc (str, arrsize*sizeof(char*));
00596         else str = (char**)malloc (arrsize*sizeof(char*));
00597         if (!str) return GLOB_NOSPACE;
00598       }
00599       str[cnt] = (char*)malloc (strlen(libdir)+1+strlen(wfd.cFileName)+1);
00600       if (!str[cnt]) return GLOB_NOSPACE;
00601       strcpy(str[cnt],libdir);
00602       strcat(str[cnt],DIRSEP);
00603       strcat(str[cnt],wfd.cFileName);
00604       cnt++;
00605     } while (FindNextFile (h, &wfd));
00606     str[cnt] = 0;
00607 
00608     pglob->gl_pathc = cnt;
00609     pglob->gl_pathv = (char**)realloc(str, (cnt+1)*sizeof(char*));
00610     
00611     return 0;
00612 }
00613 
00614 static void
00615 globfree (glob_t* pglob)
00616 {
00617     int i;
00618     for (i = 0; i < pglob->gl_pathc; i++)
00619       free (pglob->gl_pathv[i]);
00620     if (pglob->gl_pathv)
00621                 free (pglob->gl_pathv);
00622 }
00623 #endif
00624 #endif