Graphviz 13.0.0~dev.20241220.2304
Loading...
Searching...
No Matches
gvconfig.c
Go to the documentation of this file.
1/*************************************************************************
2 * Copyright (c) 2011 AT&T Intellectual Property
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors: Details at https://graphviz.org
9 *************************************************************************/
10
11#include "config.h"
12
13#ifndef _GNU_SOURCE
14#define _GNU_SOURCE 1
15#endif
16
17#include <assert.h>
18#include <cgraph/list.h>
19#include <gvc/gvconfig.h>
20#include <stdbool.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <unistd.h>
25#include <util/agxbuf.h>
26#include <util/alloc.h>
27#include <util/exit.h>
28#include <util/gv_ctype.h>
29#include <util/gv_fopen.h>
30#include <util/startswith.h>
31
32#ifdef ENABLE_LTDL
33#ifdef HAVE_DL_ITERATE_PHDR
34#include <link.h>
35#endif
36#include <sys/types.h>
37#ifdef _WIN32
38#include <windows.h>
39#define GLOB_NOSPACE 1 /* Ran out of memory. */
40#define GLOB_ABORTED 2 /* Read error. */
41#define GLOB_NOMATCH 3 /* No matches found. */
42#define GLOB_NOSORT 4
43typedef struct {
44 size_t gl_pathc; /* count of total paths so far */
45 char **gl_pathv; /* list of paths matching pattern */
46} glob_t;
47static void globfree (glob_t* pglob);
48static int glob (GVC_t * gvc, char*, int, int (*errfunc)(const char *, int), glob_t*);
49#else
50#include <glob.h>
51#endif
52#include <sys/stat.h>
53#endif
54
55#ifdef __APPLE__
56#include <mach-o/dyld.h>
57#endif
58
59#include <common/const.h>
60#include <common/types.h>
61
62#include <gvc/gvplugin.h>
63#include <gvc/gvcjob.h>
64#include <gvc/gvcint.h>
65#include <gvc/gvcproc.h>
66
67/* FIXME */
68extern void textfont_dict_open(GVC_t *gvc);
69
70/*
71 A config for gvrender is a text file containing a
72 list of plugin libraries and their capabilities using a tcl-like
73 syntax
74
75 Lines beginning with '#' are ignored as comments
76
77 Blank lines are allowed and ignored.
78
79 plugin_library_path packagename {
80 plugin_api {
81 plugin_type plugin_quality
82 ...
83 }
84 ...
85 ...
86
87 e.g.
88
89 /usr/lib/graphviz/libgvplugin_cairo.so cairo {renderer {x 0 png 10 ps -10}}
90 /usr/lib/graphviz/libgvplugin_gd.so gd {renderer {png 0 gif 0 jpg 0}}
91
92 Internally the config is maintained as lists of plugin_types for each plugin_api.
93 If multiple plugins of the same type are found then the highest quality wins.
94 If equal quality then the last-one-installed wins (thus giving preference to
95 external plugins over internal builtins).
96
97 */
98
100 const char *package_path,
101 const char *name) {
102 gvplugin_package_t *package = gv_alloc(sizeof(gvplugin_package_t));
103 package->path = package_path ? gv_strdup(package_path) : NULL;
104 package->name = gv_strdup(name);
105 package->next = gvc->packages;
106 gvc->packages = package;
107 return package;
108}
109
110#ifdef ENABLE_LTDL
111/*
112 separator - consume all non-token characters until next token. This includes:
113 comments: '#' ... '\n'
114 nesting: '{'
115 unnesting: '}'
116 whitespace: ' ','\t','\n'
117
118 *nest is changed according to nesting/unnesting processed
119 */
120static void separator(int *nest, char **tokens)
121{
122 char c, *s;
123
124 s = *tokens;
125 while ((c = *s)) {
126 /* #->eol = comment */
127 if (c == '#') {
128 s++;
129 while ((c = *s)) {
130 s++;
131 if (c == '\n')
132 break;
133 }
134 continue;
135 }
136 if (c == '{') {
137 (*nest)++;
138 s++;
139 continue;
140 }
141 if (c == '}') {
142 (*nest)--;
143 s++;
144 continue;
145 }
146 if (c == ' ' || c == '\n' || c == '\t') {
147 s++;
148 continue;
149 }
150 break;
151 }
152 *tokens = s;
153}
154
155/*
156 token - capture all characters until next separator, then consume separator,
157 return captured token, leave **tokens pointing to next token.
158 */
159static char *token(int *nest, char **tokens)
160{
161 char c, *s, *t;
162
163 s = t = *tokens;
164 while ((c = *s)) {
165 if (c == '#'
166 || c == ' ' || c == '\t' || c == '\n' || c == '{' || c == '}')
167 break;
168 s++;
169 }
170 *tokens = s;
171 separator(nest, tokens);
172 *s = '\0';
173 return t;
174}
175
176static int gvconfig_plugin_install_from_config(GVC_t * gvc, char *s)
177{
178 char *package_path, *name;
179 const char *type;
180 int quality;
181 int nest = 0;
182 gvplugin_package_t *package;
183
184 separator(&nest, &s);
185 while (*s) {
186 package_path = token(&nest, &s);
187 if (nest == 0)
188 name = token(&nest, &s);
189 else
190 name = "x";
191 package = gvplugin_package_record(gvc, package_path, name);
192 do {
193 const char *api = token(&nest, &s);
194 const api_t gv_api = gvplugin_api(api);
195 if (gv_api == (api_t)-1) {
196 agerrorf("config error: %s %s not found\n", package_path, api);
197 return 0;
198 }
199 do {
200 if (nest == 2) {
201 type = token(&nest, &s);
202 if (nest == 2)
203 quality = atoi(token(&nest, &s));
204 else
205 quality = 0;
206 bool rc = gvplugin_install(gvc, gv_api, type, quality, package, NULL);
207 if (!rc) {
208 agerrorf("config error: %s %s %s\n", package_path, api, type);
209 return 0;
210 }
211 }
212 } while (nest == 2);
213 } while (nest == 1);
214 }
215 return 1;
216}
217#endif
218
220 gvplugin_library_t *library) {
223 gvplugin_package_t *package;
224 int i;
225
226 package = gvplugin_package_record(gvc, package_path, library->packagename);
227 for (apis = library->apis; (types = apis->types); apis++) {
228 for (i = 0; types[i].type; i++) {
229 gvplugin_install(gvc, apis->api, types[i].type,
230 types[i].quality, package, &types[i]);
231 }
232 }
233}
234
236{
237 const lt_symlist_t *s;
238 const char *name;
239
240 if (gvc->common.builtins == NULL) return;
241
242 for (s = gvc->common.builtins; (name = s->name); s++)
243 if (name[0] == 'g' && strstr(name, "_LTX_library"))
245}
246
247#ifdef ENABLE_LTDL
248static void gvconfig_write_library_config(GVC_t *gvc, char *lib_path,
249 gvplugin_library_t *library,
250 FILE *f) {
252
253 fprintf(f, "%s %s {\n", lib_path, library->packagename);
254 for (gvplugin_api_t *apis = library->apis; (types = apis->types); apis++) {
255 fprintf(f, "\t%s {\n", gvplugin_api_name(apis->api));
256 for (size_t i = 0; types[i].type; i++) {
257 /* verify that dependencies are available */
258 if (!gvplugin_load(gvc, apis->api, types[i].type,
259 gvc->common.verbose > 0 ? f : NULL))
260 fprintf(f, "#FAILS");
261 fprintf(f, "\t\t%s %d\n", types[i].type, types[i].quality);
262 }
263 fputs ("\t}\n", f);
264 }
265 fputs ("}\n", f);
266}
267
268#define BSZ 1024
269#define DOTLIBS "/.libs"
270
271#ifdef HAVE_DL_ITERATE_PHDR
272static int line_callback(struct dl_phdr_info *info, size_t size, void *line)
273{
274 const char *p = info->dlpi_name;
275 char *tmp = strstr(p, "/libgvc.");
276 (void) size;
277 if (tmp) {
278 *tmp = 0;
279 /* Check for real /lib dir. Don't accept pre-install /.libs */
280 if (strcmp(strrchr(p,'/'), DOTLIBS) != 0) {
281 memmove(line, p, strlen(p) + 1); // use line buffer for result
282 strcat(line, "/graphviz"); /* plugins are in "graphviz" subdirectory */
283 return 1;
284 }
285 }
286 return 0;
287}
288#endif
289
290char * gvconfig_libdir(GVC_t * gvc)
291{
292 static char line[BSZ];
293 static char *libdir;
294 static bool dirShown = false;
295
296 if (!libdir) {
297 libdir=getenv("GVBINDIR");
298 if (!libdir) {
299#ifdef _WIN32
300 int r;
301 char* s;
302
303 MEMORY_BASIC_INFORMATION mbi;
304 if (VirtualQuery (&gvconfig_libdir, &mbi, sizeof(mbi)) == 0) {
305 agerrorf("failed to get handle for executable.\n");
306 return 0;
307 }
308 r = GetModuleFileName ((HMODULE)mbi.AllocationBase, line, BSZ);
309 if (!r || (r == BSZ)) {
310 agerrorf("failed to get path for executable.\n");
311 return 0;
312 }
313 s = strrchr(line,'\\');
314 if (!s) {
315 agerrorf("no slash in path %s.\n", line);
316 return 0;
317 }
318 *s = '\0';
319 libdir = line;
320#else
321 libdir = GVLIBDIR;
322#ifdef __APPLE__
323 uint32_t i, c = _dyld_image_count();
324 size_t len, ind;
325 for (i = 0; i < c; ++i) {
326 const char *p = _dyld_get_image_name(i);
327 const char* tmp = strstr(p, "/libgvc.");
328 if (tmp) {
329 if (tmp > p) {
330 /* Check for real /lib dir. Don't accept pre-install /.libs */
331 const char *s = tmp - 1;
332 /* back up to previous slash (or head of string) */
333 while (*s != '/' && s > p) s--;
334 if (startswith(s, DOTLIBS))
335 continue;
336 }
337
338 ind = tmp - p; // byte offset
339 len = ind + sizeof("/graphviz");
340 if (len < BSZ)
341 libdir = line;
342 else
343 libdir = gv_alloc(len);
344 if (ind > 0) {
345 memmove(libdir, p, ind);
346 }
347 /* plugins are in "graphviz" subdirectory */
348 strcpy(libdir+ind, "/graphviz");
349 break;
350 }
351 }
352#elif defined(HAVE_DL_ITERATE_PHDR)
353 dl_iterate_phdr(line_callback, line);
354 libdir = line;
355#else
356 FILE* f = gv_fopen("/proc/self/maps", "r");
357 if (f) {
358 while (!feof (f)) {
359 if (!fgets (line, sizeof (line), f))
360 continue;
361 if (!strstr (line, " r-xp "))
362 continue;
363 char *p = strchr(line, '/');
364 if (!p)
365 continue;
366 char* tmp = strstr(p, "/libgvc.");
367 if (tmp) {
368 *tmp = 0;
369 /* Check for real /lib dir. Don't accept pre-install /.libs */
370 if (strcmp(strrchr(p, '/'), "/.libs") == 0)
371 continue;
372 memmove(line, p, strlen(p) + 1); // use line buffer for result
373 strcat(line, "/graphviz"); /* plugins are in "graphviz" subdirectory */
374 libdir = line;
375 break;
376 }
377 }
378 fclose (f);
379 }
380#endif
381#endif
382 }
383 }
384 if (gvc->common.verbose && !dirShown) {
385 fprintf (stderr, "libdir = \"%s\"\n", (libdir ? libdir : "<null>"));
386 dirShown = true;
387 }
388 return libdir;
389}
390#endif
391
392#ifdef ENABLE_LTDL
393// does this path look like a Graphviz plugin of our version?
394static bool is_plugin(const char *filepath) {
395
396 if (filepath == NULL) {
397 return false;
398 }
399
400 // shared library suffix to strip before looking for version number
401#if defined(DARWIN_DYLIB)
402 static const char SUFFIX[] = ".dylib";
403#elif defined(__MINGW32__) || defined(__CYGWIN__) || defined(_WIN32)
404 static const char SUFFIX[] = ".dll";
405#else
406 static const char SUFFIX[] = "";
407#endif
408
409 // does this filename end with the expected suffix?
410 size_t len = strlen(filepath);
411 if (len < strlen(SUFFIX)
412 || strcmp(filepath + len - strlen(SUFFIX), SUFFIX) != 0) {
413 return false;
414 }
415 len -= strlen(SUFFIX);
416
417#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
418 // Windows libraries do not have a version in the filename
419
420#elif defined(GVPLUGIN_VERSION)
421 // turn GVPLUGIN_VERSION into a string
422 #define STRINGIZE_(x) #x
423 #define STRINGIZE(x) STRINGIZE_(x)
424 static const char VERSION[] = STRINGIZE(GVPLUGIN_VERSION);
425 #undef STRINGIZE
426 #undef STRINGIZE_
427
428 // does this filename contain the expected version?
429 if (len < strlen(VERSION)
430 || !startswith(filepath + len - strlen(VERSION), VERSION)) {
431 return false;
432 }
433 len -= strlen(VERSION);
434
435#else
436 // does this filename have a version?
437 if (len == 0 || !gv_isdigit(filepath[len - 1])) {
438 return false;
439 }
440 while (len > 0 && gv_isdigit(filepath[len - 1])) {
441 --len;
442 }
443
444#endif
445
446 // ensure the remainder conforms to what we expect of a shared library
447#if defined(DARWIN_DYLIB)
448 if (len < 2 || gv_isdigit(filepath[len - 2]) || filepath[len - 1] != '.') {
449 return false;
450 }
451#elif defined(__MINGW32__) || defined(__CYGWIN__)
452 if (len < 2 || gv_isdigit(filepath[len - 2]) || filepath[len - 1] != '-') {
453 return false;
454 }
455#elif defined(_WIN32)
456 if (len < 1 || gv_isdigit(filepath[len - 1])) {
457 return false;
458 }
459#elif ((defined(__hpux__) || defined(__hpux)) && !(defined(__ia64)))
460 static const char SL[] = ".sl.";
461 if (len < strlen(SL) || !startswith(filepath + len - strlen(SL), SL)) {
462 return false;
463 }
464#else
465 static const char SO[] = ".so.";
466 if (len < strlen(SO) || !startswith(filepath + len - strlen(SO), SO)) {
467 return false;
468 }
469#endif
470
471 return true;
472}
473
474static void config_rescan(GVC_t *gvc, char *config_path)
475{
476 FILE *f = NULL;
477 glob_t globbuf;
478 char *libdir;
479 int rc;
480 gvplugin_library_t *library;
481#if defined(DARWIN_DYLIB)
482 char *plugin_glob = "libgvplugin_*";
483#elif defined(__MINGW32__)
484 char *plugin_glob = "libgvplugin_*";
485#elif defined(__CYGWIN__)
486 char *plugin_glob = "cyggvplugin_*";
487#elif defined(_WIN32)
488 char *plugin_glob = "gvplugin_*";
489#else
490 char *plugin_glob = "libgvplugin_*";
491#endif
492
493 if (config_path) {
494 f = gv_fopen(config_path, "w");
495 if (!f) {
496 agerrorf("failed to open %s for write.\n", config_path);
497 graphviz_exit(1);
498 }
499
500 fprintf(f, "# This file was generated by \"dot -c\" at time of install.\n\n");
501 fprintf(f, "# You may temporarily disable a plugin by removing or commenting out\n");
502 fprintf(f, "# a line in this file, or you can modify its \"quality\" value to affect\n");
503 fprintf(f, "# default plugin selection.\n\n");
504 fprintf(f, "# Manual edits to this file **will be lost** on upgrade.\n\n");
505 }
506
507 libdir = gvconfig_libdir(gvc);
508
509 agxbuf config_glob = {0};
510 agxbprint(&config_glob, "%s%s%s", libdir, DIRSEP, plugin_glob);
511
512 /* load all libraries even if can't save config */
513
514#if defined(_WIN32)
515 rc = glob(gvc, agxbuse(&config_glob), GLOB_NOSORT, NULL, &globbuf);
516#else
517 rc = glob(agxbuse(&config_glob), 0, NULL, &globbuf);
518#endif
519 if (rc == 0) {
520 for (size_t i = 0; i < globbuf.gl_pathc; i++) {
521 if (is_plugin(globbuf.gl_pathv[i])) {
522 library = gvplugin_library_load(gvc, globbuf.gl_pathv[i]);
523 if (library) {
524 gvconfig_plugin_install_from_library(gvc, globbuf.gl_pathv[i], library);
525 }
526 }
527 }
528 /* rescan with all libs loaded to check cross dependencies */
529 for (size_t i = 0; i < globbuf.gl_pathc; i++) {
530 if (is_plugin(globbuf.gl_pathv[i])) {
531 library = gvplugin_library_load(gvc, globbuf.gl_pathv[i]);
532 if (library) {
533 char *p = strrchr(globbuf.gl_pathv[i], DIRSEP[0]);
534 if (p)
535 p++;
536 if (f && p)
537 gvconfig_write_library_config(gvc, p, library, f);
538 }
539 }
540 }
541 }
542 globfree(&globbuf);
543 agxbfree(&config_glob);
544 if (f)
545 fclose(f);
546}
547#endif
548
549/*
550 gvconfig - parse a config file and install the identified plugins
551 */
552void gvconfig(GVC_t * gvc, bool rescan)
553{
554#ifdef ENABLE_LTDL
555 int rc;
556 struct stat config_st;
557 FILE *f = NULL;
558 char *config_text = NULL;
559 char *libdir;
560 char *config_file_name = GVPLUGIN_CONFIG_FILE;
561
562#endif
563
564 /* builtins don't require LTDL */
566
567 gvc->config_found = false;
568#ifdef ENABLE_LTDL
570 /* see if there are any new plugins */
571 libdir = gvconfig_libdir(gvc);
572 if (access(libdir, F_OK) < 0) {
573 /* if we fail to stat it then it probably doesn't exist so just fail silently */
574 goto done;
575 }
576
577 if (! gvc->config_path) {
578 agxbuf xb = {0};
579 agxbprint(&xb, "%s%s%s", libdir, DIRSEP, config_file_name);
580 gvc->config_path = agxbdisown(&xb);
581 }
582
583 if (rescan) {
584 config_rescan(gvc, gvc->config_path);
585 gvc->config_found = true;
586 gvtextlayout_select(gvc); /* choose best available textlayout plugin immediately */
587 assert(gvc->textfont_dt != NULL &&
588 "config rescan performed without any prior first scan");
589 return;
590 }
591
592 /* load in the cached plugin library data */
593
594 rc = stat(gvc->config_path, &config_st);
595 if (rc == -1) {
596 /* silently return without setting gvc->config_found = TRUE */
597 goto done;
598 }
599 else {
600 f = gv_fopen(gvc->config_path, "r");
601 if (!f) {
602 agerrorf("failed to open %s for read.\n", gvc->config_path);
603 return;
604 }
605 else if (config_st.st_size == 0) {
606 agerrorf("%s is zero sized.\n", gvc->config_path);
607 }
608 else {
609 config_text = gv_alloc((size_t)config_st.st_size + 1);
610 size_t sz = fread(config_text, 1, (size_t)config_st.st_size, f);
611 if (sz == 0) {
612 agerrorf("%s read error.\n", gvc->config_path);
613 }
614 else {
615 gvc->config_found = true;
616 config_text[sz] = '\0'; /* make input into a null terminated string */
617 rc = gvconfig_plugin_install_from_config(gvc, config_text);
618 }
619 free(config_text);
620 }
621 if (f) {
622 fclose(f);
623 }
624 }
625 }
626done:
627#endif
628 gvtextlayout_select(gvc); /* choose best available textlayout plugin immediately */
629 textfont_dict_open(gvc); /* initialize font dict */
630}
631
632#ifdef ENABLE_LTDL
633#ifdef _WIN32
634
635/* Emulating windows glob */
636
637DEFINE_LIST_WITH_DTOR(strs, char *, free)
638
639/* glob:
640 * Assumes only GLOB_NOSORT flag given. That is, there is no offset,
641 * and no previous call to glob.
642 */
643
644static int
645glob (GVC_t* gvc, char* pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob)
646{
647 char* libdir;
648 WIN32_FIND_DATA wfd;
649 HANDLE h;
650 strs_t strs = {0};
651
652 pglob->gl_pathc = 0;
653 pglob->gl_pathv = NULL;
654
655 h = FindFirstFile (pattern, &wfd);
656 if (h == INVALID_HANDLE_VALUE) return GLOB_NOMATCH;
657 libdir = gvconfig_libdir(gvc);
658 do {
659 const size_t size =
660 strlen(libdir) + strlen(DIRSEP) + strlen(wfd.cFileName) + 1;
661 char *const entry = malloc(size);
662 if (!entry) {
663 goto oom;
664 }
665 snprintf(entry, size, "%s%s%s", libdir, DIRSEP, wfd.cFileName);
666 if (strs_try_append(&strs, entry) != 0) {
667 free(entry);
668 goto oom;
669 }
670 } while (FindNextFile (h, &wfd));
671 if (strs_try_append(&strs, NULL) != 0) {
672 goto oom;
673 }
674
675 pglob->gl_pathc = strs_size(&strs);
676 pglob->gl_pathv = strs_detach(&strs);
677
678 return 0;
679
680oom:
681 strs_free(&strs);
682 return GLOB_NOSPACE;
683}
684
685static void
686globfree (glob_t* pglob)
687{
688 int i;
689 for (i = 0; i < pglob->gl_pathc; i++)
690 free (pglob->gl_pathv[i]);
691 free (pglob->gl_pathv);
692}
693#endif
694#endif
static void agxbfree(agxbuf *xb)
free any malloced resources
Definition agxbuf.h:78
static int agxbprint(agxbuf *xb, const char *fmt,...)
Printf-style output to an agxbuf.
Definition agxbuf.h:234
static WUR char * agxbuse(agxbuf *xb)
Definition agxbuf.h:307
static char * agxbdisown(agxbuf *xb)
Definition agxbuf.h:327
Memory allocation wrappers that exit on failure.
static char * gv_strdup(const char *original)
Definition alloc.h:101
static void * gv_alloc(size_t size)
Definition alloc.h:47
static NORETURN void graphviz_exit(int status)
Definition exit.h:23
expr procedure type
Definition exparse.y:208
static int flags
Definition gc.c:61
static double len(glCompPoint p)
Definition glutils.c:150
void * malloc(YYSIZE_T)
void free(void *)
node NULL
Definition grammar.y:163
void agerrorf(const char *fmt,...)
Definition agerror.c:165
static GVC_t * gvc
Definition gv.cpp:23
replacements for ctype.h functions
static bool gv_isdigit(int c)
Definition gv_ctype.h:41
FILE * gv_fopen(const char *filename, const char *mode)
Definition gv_fopen.c:32
wrapper around fopen for internal library usage
api_t
Definition gvcext.h:32
#define DIRSEP
Definition gvcint.h:163
static gvplugin_package_t * gvplugin_package_record(GVC_t *gvc, const char *package_path, const char *name)
Definition gvconfig.c:99
void gvconfig(GVC_t *gvc, bool rescan)
Definition gvconfig.c:552
void textfont_dict_open(GVC_t *gvc)
Definition textspan.c:160
void gvconfig_plugin_install_from_library(GVC_t *gvc, char *package_path, gvplugin_library_t *library)
Definition gvconfig.c:219
static void gvconfig_plugin_install_builtins(GVC_t *gvc)
Definition gvconfig.c:235
char * gvconfig_libdir(GVC_t *gvc)
gvplugin_library_t * gvplugin_library_load(GVC_t *gvc, const char *pathname)
Definition gvplugin.c:153
api_t gvplugin_api(const char *str)
Definition gvplugin.c:50
gvplugin_available_t * gvplugin_load(GVC_t *gvc, api_t api, const char *type, FILE *debug)
Definition gvplugin.c:250
int gvtextlayout_select(GVC_t *gvc)
char * gvplugin_api_name(api_t api)
Definition gvplugin.c:60
bool gvplugin_install(GVC_t *gvc, api_t api, const char *typestr, int quality, gvplugin_package_t *package, gvplugin_installed_t *typeptr)
Definition gvplugin.c:72
static gvplugin_api_t apis[]
#define BSZ
Definition layout.c:277
#define DEFINE_LIST_WITH_DTOR(name, type, dtor)
Definition list.h:34
static bool startswith(const char *s, const char *prefix)
does the string s begin with the string prefix?
Definition startswith.h:11
const lt_symlist_t * builtins
Definition gvcommon.h:31
int demand_loading
Definition gvcommon.h:32
int verbose
Definition gvcommon.h:22
Definition gvcint.h:80
char * config_path
Definition gvcint.h:83
bool config_found
Definition gvcint.h:84
GVCOMMON_t common
Definition gvcint.h:81
gvplugin_package_t * packages
Definition gvcint.h:100
Dt_t * textfont_dt
Definition gvcint.h:107
gvplugin_installed_t * types
Definition gvplugin.h:53
ingroup plugin_api
Definition gvplugin.h:35
const char * type
Definition gvplugin.h:41
gvplugin_api_t * apis
Definition gvplugin.h:59
graphs, nodes and edges info: Agraphinfo_t, Agnodeinfo_t and Agedgeinfo_t
Definition grammar.c:93
char * name
Definition grammar.c:98