gvconfig.c 18 KB

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