xref: /petsc/src/sys/dll/dl.c (revision 6d8694c4fbab79f9439f1ad13c0386ba7ee1ca4b)
1 /*
2       Routines for opening dynamic link libraries (DLLs), keeping a searchable
3    path of DLLs, obtaining remote DLLs via a URL and opening them locally.
4 */
5 
6 #include <petsc/private/petscimpl.h>
7 
8 /*
9       Code to maintain a list of opened dynamic libraries and load symbols
10 */
11 struct _n_PetscDLLibrary {
12   PetscDLLibrary next;
13   PetscDLHandle  handle;
14   char           libname[PETSC_MAX_PATH_LEN];
15 };
16 
PetscDLLibraryPrintPath(PetscDLLibrary libs)17 PetscErrorCode PetscDLLibraryPrintPath(PetscDLLibrary libs)
18 {
19   PetscFunctionBegin;
20   while (libs) {
21     PetscCall(PetscErrorPrintf("  %s\n", libs->libname));
22     libs = libs->next;
23   }
24   PetscFunctionReturn(PETSC_SUCCESS);
25 }
26 
27 /*@C
28   PetscDLLibraryRetrieve - Copies a PETSc dynamic library from a remote location
29   (if it is remote), then indicates if it exits and its local name.
30 
31   Collective
32 
33   Input Parameters:
34 + comm    - MPI processes that will be opening the library
35 . libname - name of the library, can be a relative or absolute path and be a URL
36 - llen    - length of the `name` buffer
37 
38   Output Parameters:
39 + lname - actual name of the file on local filesystem if `found`
40 - found - true if the file exists
41 
42   Level: developer
43 
44   Notes:
45   [[<http,ftp>://hostname]/directoryname/]filename[.so.1.0]
46 
47    ${PETSC_ARCH}, ${PETSC_DIR}, ${PETSC_LIB_DIR}, or ${any environmental variable}
48   occurring in directoryname and filename will be replaced with appropriate values.
49 
50 .seealso: `PetscFileRetrieve()`
51 @*/
PetscDLLibraryRetrieve(MPI_Comm comm,const char libname[],char lname[],size_t llen,PetscBool * found)52 PetscErrorCode PetscDLLibraryRetrieve(MPI_Comm comm, const char libname[], char lname[], size_t llen, PetscBool *found)
53 {
54   char  *buf, *par2, *gz = NULL, *so = NULL;
55   size_t len, blen;
56 
57   PetscFunctionBegin;
58   /*
59      make copy of library name and replace $PETSC_ARCH etc
60      so we can add to the end of it to look for something like .so.1.0 etc.
61   */
62   PetscCall(PetscStrlen(libname, &len));
63   blen = PetscMax(4 * len, PETSC_MAX_PATH_LEN);
64   PetscCall(PetscMalloc1(blen, &buf));
65   par2 = buf;
66   PetscCall(PetscStrreplace(comm, libname, par2, blen));
67 
68   /* temporarily remove .gz if it ends library name */
69   PetscCall(PetscStrrstr(par2, ".gz", &gz));
70   if (gz) {
71     PetscCall(PetscStrlen(gz, &len));
72     if (len != 3) gz = NULL; /* do not end (exactly) with .gz */
73     else *gz = 0;            /* ends with .gz, so remove it   */
74   }
75   /* strip out .a from it if user put it in by mistake */
76   PetscCall(PetscStrlen(par2, &len));
77   if (par2[len - 1] == 'a' && par2[len - 2] == '.') par2[len - 2] = 0;
78 
79   PetscCall(PetscFileRetrieve(comm, par2, lname, llen, found));
80   if (!*found) {
81     const char suffix[] = "." PETSC_SLSUFFIX;
82 
83     /* see if library name does already not have suffix attached */
84     PetscCall(PetscStrrstr(par2, suffix, &so));
85     /* and attach the suffix if it is not there */
86     if (!so) PetscCall(PetscStrlcat(par2, suffix, blen));
87 
88     /* restore the .gz suffix if it was there */
89     if (gz) PetscCall(PetscStrlcat(par2, ".gz", blen));
90 
91     /* and finally retrieve the file */
92     PetscCall(PetscFileRetrieve(comm, par2, lname, llen, found));
93   }
94 
95   PetscCall(PetscFree(buf));
96   PetscFunctionReturn(PETSC_SUCCESS);
97 }
98 
99 /*@C
100   PetscDLLibraryOpen - Opens a PETSc dynamic link library
101 
102   Collective, No Fortran Support
103 
104   Input Parameters:
105 + comm - MPI processes that are opening the library
106 - path - name of the library, can be a relative or absolute path
107 
108   Output Parameter:
109 . entry - a PETSc dynamic link library entry
110 
111   Level: developer
112 
113   Notes:
114   [[<http,ftp>://hostname]/directoryname/]libbasename[.so.1.0]
115 
116   If the library has the symbol `PetscDLLibraryRegister_basename()` in it then that function is automatically run
117   when the library is opened.
118 
119    ${PETSC_ARCH} occurring in directoryname and filename
120   will be replaced with the appropriate value.
121 
122 .seealso: `PetscDLLibrary`, `PetscLoadDynamicLibrary()`, `PetscDLLibraryAppend()`, `PetscDLLibraryRetrieve()`, `PetscDLLibrarySym()`, `PetscDLLibraryClose()`
123 @*/
PetscDLLibraryOpen(MPI_Comm comm,const char path[],PetscDLLibrary * entry)124 PetscErrorCode PetscDLLibraryOpen(MPI_Comm comm, const char path[], PetscDLLibrary *entry)
125 {
126   PetscBool     foundlibrary, match;
127   const char    suffix[] = "." PETSC_SLSUFFIX;
128   char          libname[PETSC_MAX_PATH_LEN], par2[PETSC_MAX_PATH_LEN], *s;
129   char         *basename, registername[128];
130   PetscDLHandle handle;
131   PetscErrorCode (*func)(void) = NULL;
132 
133   PetscFunctionBegin;
134   PetscAssertPointer(path, 2);
135   PetscAssertPointer(entry, 3);
136 
137   *entry = NULL;
138 
139   /* retrieve the library */
140   PetscCall(PetscInfo(NULL, "Retrieving %s\n", path));
141   PetscCall(PetscDLLibraryRetrieve(comm, path, par2, PETSC_MAX_PATH_LEN, &foundlibrary));
142   PetscCheck(foundlibrary, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to locate dynamic library: %s", path);
143   /* Eventually ./configure should determine if the system needs an executable dynamic library */
144 #define PETSC_USE_NONEXECUTABLE_SO
145 #if !defined(PETSC_USE_NONEXECUTABLE_SO)
146   PetscCall(PetscTestFile(par2, 'x', &foundlibrary));
147   PetscCheck(foundlibrary, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Dynamic library is not executable: %s %s", path, par2);
148 #endif
149 
150   /* copy path and setup shared library suffix  */
151   PetscCall(PetscStrncpy(libname, path, sizeof(libname)));
152   /* remove wrong suffixes from libname */
153   PetscCall(PetscStrrstr(libname, ".gz", &s));
154   if (s && s[3] == 0) s[0] = 0;
155   PetscCall(PetscStrrstr(libname, ".a", &s));
156   if (s && s[2] == 0) s[0] = 0;
157   /* remove shared suffix from libname */
158   PetscCall(PetscStrrstr(libname, suffix, &s));
159   if (s) s[0] = 0;
160 
161   /* open the dynamic library */
162   PetscCall(PetscInfo(NULL, "Opening dynamic library %s\n", libname));
163   PetscCall(PetscDLOpen(par2, PETSC_DL_DECIDE, &handle));
164 
165   /* look for [path/]libXXXXX.YYY and extract out the XXXXXX */
166   PetscCall(PetscStrrchr(libname, '/', &basename)); /* XXX Windows ??? */
167   if (!basename) basename = libname;
168   PetscCall(PetscStrncmp(basename, "lib", 3, &match));
169   if (match) basename = basename + 3;
170   else PetscCall(PetscInfo(NULL, "Dynamic library %s does not have lib prefix\n", libname));
171   for (s = basename; *s; s++)
172     if (*s == '-') *s = '_';
173   PetscCall(PetscStrncpy(registername, "PetscDLLibraryRegister_", sizeof(registername)));
174   PetscCall(PetscStrlcat(registername, basename, sizeof(registername)));
175   PetscCall(PetscDLSym(handle, registername, (void **)&func));
176   if (func) {
177     PetscCall(PetscInfo(NULL, "Loading registered routines from %s\n", libname));
178     PetscCall((*func)());
179   } else {
180     PetscCall(PetscInfo(NULL, "Dynamic library %s does not have symbol %s\n", libname, registername));
181   }
182 
183   PetscCall(PetscNew(entry));
184   (*entry)->next   = NULL;
185   (*entry)->handle = handle;
186   PetscCall(PetscStrncpy((*entry)->libname, libname, sizeof((*entry)->libname)));
187   PetscFunctionReturn(PETSC_SUCCESS);
188 }
189 
190 /*@C
191   PetscDLLibrarySym - Load a symbol from a list of dynamic link libraries.
192 
193   Collective, No Fortran Support
194 
195   Input Parameters:
196 + comm     - the MPI communicator that will load the symbol
197 . outlist  - list of already open libraries that may contain symbol (can be `NULL` and only the executable is searched for the function)
198 . path     - optional complete library name (if provided it checks here before checking `outlist`)
199 - insymbol - name of symbol
200 
201   Output Parameter:
202 . value - if symbol not found then this value is set to `NULL`
203 
204   Level: developer
205 
206   Notes:
207   Symbol can be of the form
208   [/path/libname[.so.1.0]:]functionname[()] where items in [] denote optional
209 
210   It will attempt to (retrieve and) open the library if it is not yet been opened.
211 
212 .seealso: `PetscDLLibrary`, `PetscLoadDynamicLibrary()`, `PetscDLLibraryAppend()`, `PetscDLLibraryRetrieve()`, `PetscDLLibraryOpen()`, `PetscDLLibraryClose()`
213 @*/
PetscDLLibrarySym(MPI_Comm comm,PetscDLLibrary * outlist,const char path[],const char insymbol[],void ** value)214 PetscErrorCode PetscDLLibrarySym(MPI_Comm comm, PetscDLLibrary *outlist, const char path[], const char insymbol[], void **value) PeNS
215 {
216   char           libname[PETSC_MAX_PATH_LEN], suffix[16];
217   char          *symbol = NULL, *s = NULL;
218   PetscDLLibrary list = NULL, nlist, prev;
219 
220   PetscFunctionBegin;
221   if (outlist) PetscAssertPointer(outlist, 2);
222   if (path) PetscAssertPointer(path, 3);
223   PetscAssertPointer(insymbol, 4);
224   PetscAssertPointer(value, 5);
225 
226   if (outlist) list = *outlist;
227   *value = NULL;
228 
229   PetscCall(PetscStrchr(insymbol, '(', &s));
230   if (s) {
231     /* make copy of symbol so we can edit it in place */
232     PetscCall(PetscStrallocpy(insymbol, &symbol));
233     /* If symbol contains () then replace with a NULL, to support functionname() */
234     PetscCall(PetscStrchr(symbol, '(', &s));
235     s[0] = 0;
236   } else symbol = (char *)insymbol;
237 
238   /*
239        Function name does include library
240   */
241   if (path && path[0] != '\0') {
242     /* copy path and remove suffix from libname */
243     PetscCall(PetscStrncpy(libname, path, PETSC_MAX_PATH_LEN));
244     PetscCall(PetscStrncpy(suffix, ".", sizeof(suffix)));
245     PetscCall(PetscStrlcat(suffix, PETSC_SLSUFFIX, sizeof(suffix)));
246     PetscCall(PetscStrrstr(libname, suffix, &s));
247     if (s) s[0] = 0;
248     /* Look if library is already opened and in path */
249     prev  = NULL;
250     nlist = list;
251     while (nlist) {
252       PetscBool match;
253       PetscCall(PetscStrcmp(nlist->libname, libname, &match));
254       if (match) goto done;
255       prev  = nlist;
256       nlist = nlist->next;
257     }
258     /* open the library and append it to path */
259     PetscCall(PetscDLLibraryOpen(comm, path, &nlist));
260     PetscCall(PetscInfo(NULL, "Appending %s to dynamic library search path\n", path));
261     if (prev) prev->next = nlist;
262     else {
263       if (outlist) *outlist = nlist;
264     }
265 
266   done:;
267     PetscCall(PetscDLSym(nlist->handle, symbol, value));
268     if (*value) PetscCall(PetscInfo(NULL, "Loading function %s from dynamic library %s\n", insymbol, path));
269 
270     /*
271          Function name does not include library so search path
272     */
273   } else {
274     while (list) {
275       PetscCall(PetscDLSym(list->handle, symbol, value));
276       if (*value) {
277         PetscCall(PetscInfo(NULL, "Loading symbol %s from dynamic library %s\n", symbol, list->libname));
278         break;
279       }
280       list = list->next;
281     }
282     if (!*value) {
283       PetscCall(PetscDLSym(NULL, symbol, value));
284       if (*value) PetscCall(PetscInfo(NULL, "Loading symbol %s from object code\n", symbol));
285     }
286   }
287 
288   if (symbol != insymbol) PetscCall(PetscFree(symbol));
289   PetscFunctionReturn(PETSC_SUCCESS);
290 }
291 
292 /*@C
293   PetscDLLibraryAppend - Appends another dynamic link library to the end  of the search list
294 
295   Collective, No Fortran Support
296 
297   Input Parameters:
298 + comm - MPI communicator
299 - path - name of the library
300 
301   Output Parameter:
302 . outlist - list of libraries
303 
304   Level: developer
305 
306   Note:
307   if library is already in path will not add it.
308 
309   If the library has the symbol PetscDLLibraryRegister_basename() in it then that function is automatically run
310   when the library is opened.
311 
312 .seealso: `PetscDLLibrary`, `PetscDLLibraryOpen()`, `PetscLoadDynamicLibrary()`, `PetscDLLibraryRetrieve()`, `PetscDLLibraryPrepend()`
313 @*/
PetscDLLibraryAppend(MPI_Comm comm,PetscDLLibrary * outlist,const char path[])314 PetscErrorCode PetscDLLibraryAppend(MPI_Comm comm, PetscDLLibrary *outlist, const char path[])
315 {
316   PetscDLLibrary list, prev;
317   size_t         len;
318   PetscBool      match, dir;
319   char           program[PETSC_MAX_PATH_LEN], found[8 * PETSC_MAX_PATH_LEN];
320   const char    *libname;
321   char           suffix[16], *s = NULL;
322   PetscToken     token;
323 
324   PetscFunctionBegin;
325   PetscAssertPointer(outlist, 2);
326 
327   /* is path a directory? */
328   PetscCall(PetscTestDirectory(path, 'r', &dir));
329   if (dir) {
330     PetscCall(PetscInfo(NULL, "Checking directory %s for dynamic libraries\n", path));
331     PetscCall(PetscStrncpy(program, path, sizeof(program)));
332     PetscCall(PetscStrlen(program, &len));
333     if (program[len - 1] == '/') {
334       PetscCall(PetscStrlcat(program, "*.", sizeof(program)));
335     } else {
336       PetscCall(PetscStrlcat(program, "/*.", sizeof(program)));
337     }
338     PetscCall(PetscStrlcat(program, PETSC_SLSUFFIX, sizeof(program)));
339 
340     PetscCall(PetscLs(comm, program, found, 8 * PETSC_MAX_PATH_LEN, &dir));
341     if (!dir) PetscFunctionReturn(PETSC_SUCCESS);
342   } else {
343     PetscCall(PetscStrncpy(found, path, PETSC_MAX_PATH_LEN));
344   }
345   PetscCall(PetscStrncpy(suffix, ".", sizeof(suffix)));
346   PetscCall(PetscStrlcat(suffix, PETSC_SLSUFFIX, sizeof(suffix)));
347 
348   PetscCall(PetscTokenCreate(found, '\n', &token));
349   PetscCall(PetscTokenFind(token, &libname));
350   while (libname) {
351     /* remove suffix from libname */
352     PetscCall(PetscStrrstr(libname, suffix, &s));
353     if (s) s[0] = 0;
354     /* see if library was already open then we are done */
355     list = prev = *outlist;
356     match       = PETSC_FALSE;
357     while (list) {
358       PetscCall(PetscStrcmp(list->libname, libname, &match));
359       if (match) break;
360       prev = list;
361       list = list->next;
362     }
363     /* restore suffix from libname */
364     if (s) s[0] = '.';
365     if (!match) {
366       /* open the library and add to end of list */
367       PetscCall(PetscDLLibraryOpen(comm, libname, &list));
368       PetscCall(PetscInfo(NULL, "Appending %s to dynamic library search path\n", libname));
369       if (!*outlist) *outlist = list;
370       else prev->next = list;
371     }
372     PetscCall(PetscTokenFind(token, &libname));
373   }
374   PetscCall(PetscTokenDestroy(&token));
375   PetscFunctionReturn(PETSC_SUCCESS);
376 }
377 
378 /*@C
379   PetscDLLibraryPrepend - Add another dynamic library to search for symbols to the beginning of the search list
380 
381   Collective, No Fortran Support
382 
383   Input Parameters:
384 + comm - MPI communicator
385 - path - name of the library
386 
387   Output Parameter:
388 . outlist - list of libraries
389 
390   Level: developer
391 
392   Note:
393   If library is already in the list it will remove the old reference.
394 
395 .seealso: `PetscDLLibrary`, `PetscDLLibraryOpen()`, `PetscLoadDynamicLibrary()`, `PetscDLLibraryRetrieve()`, `PetscDLLibraryAppend()`
396 @*/
PetscDLLibraryPrepend(MPI_Comm comm,PetscDLLibrary * outlist,const char path[])397 PetscErrorCode PetscDLLibraryPrepend(MPI_Comm comm, PetscDLLibrary *outlist, const char path[])
398 {
399   PetscDLLibrary list, prev;
400   size_t         len;
401   PetscBool      match, dir;
402   char           program[PETSC_MAX_PATH_LEN], found[8 * PETSC_MAX_PATH_LEN];
403   const char    *libname;
404   char           suffix[16], *s = NULL;
405   PetscToken     token;
406 
407   PetscFunctionBegin;
408   PetscAssertPointer(outlist, 2);
409 
410   /* is path a directory? */
411   PetscCall(PetscTestDirectory(path, 'r', &dir));
412   if (dir) {
413     PetscCall(PetscInfo(NULL, "Checking directory %s for dynamic libraries\n", path));
414     PetscCall(PetscStrncpy(program, path, sizeof(program)));
415     PetscCall(PetscStrlen(program, &len));
416     if (program[len - 1] == '/') {
417       PetscCall(PetscStrlcat(program, "*.", sizeof(program)));
418     } else {
419       PetscCall(PetscStrlcat(program, "/*.", sizeof(program)));
420     }
421     PetscCall(PetscStrlcat(program, PETSC_SLSUFFIX, sizeof(program)));
422 
423     PetscCall(PetscLs(comm, program, found, 8 * PETSC_MAX_PATH_LEN, &dir));
424     if (!dir) PetscFunctionReturn(PETSC_SUCCESS);
425   } else {
426     PetscCall(PetscStrncpy(found, path, PETSC_MAX_PATH_LEN));
427   }
428 
429   PetscCall(PetscStrncpy(suffix, ".", sizeof(suffix)));
430   PetscCall(PetscStrlcat(suffix, PETSC_SLSUFFIX, sizeof(suffix)));
431 
432   PetscCall(PetscTokenCreate(found, '\n', &token));
433   PetscCall(PetscTokenFind(token, &libname));
434   while (libname) {
435     /* remove suffix from libname */
436     PetscCall(PetscStrstr(libname, suffix, &s));
437     if (s) s[0] = 0;
438     /* see if library was already open and move it to the front */
439     prev  = NULL;
440     list  = *outlist;
441     match = PETSC_FALSE;
442     while (list) {
443       PetscCall(PetscStrcmp(list->libname, libname, &match));
444       if (match) {
445         PetscCall(PetscInfo(NULL, "Moving %s to begin of dynamic library search path\n", libname));
446         if (prev) prev->next = list->next;
447         if (prev) list->next = *outlist;
448         *outlist = list;
449         break;
450       }
451       prev = list;
452       list = list->next;
453     }
454     /* restore suffix from libname */
455     if (s) s[0] = '.';
456     if (!match) {
457       /* open the library and add to front of list */
458       PetscCall(PetscDLLibraryOpen(comm, libname, &list));
459       PetscCall(PetscInfo(NULL, "Prepending %s to dynamic library search path\n", libname));
460       list->next = *outlist;
461       *outlist   = list;
462     }
463     PetscCall(PetscTokenFind(token, &libname));
464   }
465   PetscCall(PetscTokenDestroy(&token));
466   PetscFunctionReturn(PETSC_SUCCESS);
467 }
468 
469 /*@C
470   PetscDLLibraryClose - Destroys the search path of dynamic libraries and closes the libraries.
471 
472   Collective, No Fortran Support
473 
474   Input Parameter:
475 . list - library list
476 
477   Level: developer
478 
479 .seealso: `PetscDLLibrary`, `PetscDLLibraryOpen()`, `PetscLoadDynamicLibrary()`, `PetscDLLibraryRetrieve()`, `PetscDLLibraryAppend()`,
480           `PetscDLLibraryPrepend()`
481 @*/
PetscDLLibraryClose(PetscDLLibrary list)482 PetscErrorCode PetscDLLibraryClose(PetscDLLibrary list)
483 {
484   PetscBool      done = PETSC_FALSE;
485   PetscDLLibrary prev, tail;
486 
487   PetscFunctionBegin;
488   if (!list) PetscFunctionReturn(PETSC_SUCCESS);
489   /* traverse the list in reverse order */
490   while (!done) {
491     if (!list->next) done = PETSC_TRUE;
492     prev = tail = list;
493     while (tail->next) {
494       prev = tail;
495       tail = tail->next;
496     }
497     prev->next = NULL;
498     /* close the dynamic library and free the space in entry data-structure*/
499     PetscCall(PetscInfo(NULL, "Closing dynamic library %s\n", tail->libname));
500     PetscCall(PetscDLClose(&tail->handle));
501     PetscCall(PetscFree(tail));
502   }
503   PetscFunctionReturn(PETSC_SUCCESS);
504 }
505