/[ascend]/trunk/base/generic/utilities/ascDynaLoad.c
ViewVC logotype

Contents of /trunk/base/generic/utilities/ascDynaLoad.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1949 - (show annotations) (download) (as text)
Mon Nov 10 01:41:41 2008 UTC (15 years, 7 months ago) by jpye
File MIME type: text/x-csrc
File size: 18591 byte(s)
Fixed tcl-config for Windows (working with ActiveState Tcl 8.4)
Removed debug output from ascDynaload.
1 /* ASCEND modelling environment
2 Copyright (C) 2006 Carnegie Mellon University
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
7 any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place - Suite 330,
17 Boston, MA 02111-1307, USA.
18 *//**
19 @file
20
21 This file *should* support unix/linux-style systems (dlfcn.h)
22 and Windows.
23
24 Note that under many systems, profiling does not work
25 with dynamic libraries!
26 */
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <stdarg.h>
31 #include "ascConfig.h"
32 #include "error.h"
33 #include "ascPrint.h"
34 #include "ascPanic.h"
35 #include "ascMalloc.h"
36 #include "ascDynaLoad.h"
37 #include "ascEnvVar.h"
38
39 #include <general/env.h>
40 #include <general/ospath.h>
41 #include <compiler/instance_enum.h>
42 #include <general/list.h>
43 #include <compiler/extfunc.h>
44 #include <compiler/importhandler.h>
45
46 typedef int (*ExternalLibraryRegister_fptr_t)(void);
47
48 /*--------------------------------------
49 GENERIC STUFF
50 */
51
52 struct ascend_dlrecord {
53 char *path; /* library name */
54 void *dlreturn; /* return from dlopen */
55 struct ascend_dlrecord *next;
56 };
57
58 /* Linked list of library names & dlopen() return values. */
59 static struct ascend_dlrecord *g_ascend_dllist = NULL;
60
61 /*
62 * Adds a record of the path and handle to the list.
63 * If it fails to do this, returns 1, else 0.
64 */
65 static int AscAddRecord(void *dlreturn, CONST char *path){
66 struct ascend_dlrecord *new;
67 char *keeppath;
68 if (dlreturn == NULL || path == NULL) {
69 return 1;
70 }
71 keeppath = ASC_STRDUP((char *)path);
72 if (keeppath==NULL) return 1;
73 new = ASC_NEW(struct ascend_dlrecord);
74 if (new==NULL) {
75 ascfree(keeppath);
76 return 1;
77 }
78 new->next = g_ascend_dllist; /* insert at head */
79 g_ascend_dllist = new;
80 new->path = keeppath;
81 new->dlreturn = dlreturn;
82 return 0;
83 }
84
85 /*
86 * Finds a record of the path given and returns the associated handle.
87 * If it fails to do this, returns NULL.
88 */
89 static
90 void *AscFindDLRecord(CONST char *path)
91 {
92 struct ascend_dlrecord *new;
93 if (path == NULL) {
94 return NULL;
95 }
96 new = g_ascend_dllist;
97 while (new != NULL && strcmp(new->path,path) != 0) {
98 /* advance new until no more new or new with path found */
99 new = new->next;
100 }
101 return (new != NULL) ? new->dlreturn : NULL;
102 }
103
104 /*
105 * Finds and returns the handle to path, if one matches, and
106 * deletes the record from the list. Returns NULL if not found.
107 */
108 static
109 void *AscDeleteRecord(CONST char *path)
110 {
111 struct ascend_dlrecord *nextptr, *lastptr, *old;
112 void *dlreturn = NULL;
113
114 if ((g_ascend_dllist == NULL) || (NULL == path)) return NULL;
115
116 if (strcmp(path,g_ascend_dllist->path)==0) {
117 /* head case */
118 old = g_ascend_dllist;
119 g_ascend_dllist = old->next;
120 dlreturn = old->dlreturn;
121 ascfree(old->path);
122 ascfree(old);
123 } else {
124 lastptr = g_ascend_dllist;
125 nextptr = lastptr->next;
126 while (nextptr != NULL && strcmp(nextptr->path,path) != 0) {
127 lastptr = nextptr;
128 nextptr = nextptr->next;
129 }
130 /* so either nextptr is NULL and not in list, or nextptr is
131 * what we want to delete and lastptr is the link to it.
132 */
133 if (nextptr != NULL) {
134 old = nextptr;
135 lastptr->next = nextptr->next;
136 dlreturn = old->dlreturn;
137 ascfree(old->path);
138 ascfree(old);
139 }
140 }
141 return dlreturn;
142 }
143
144 /*
145 * Checks the list for a conflicting handle so we can issue
146 * a more helpful warning, if need be, than the standard message.
147 */
148 static
149 void AscCheckDuplicateLoad(CONST char *path)
150 {
151 struct ascend_dlrecord *r;
152
153 if (NULL == path) {
154 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Null path");
155 return;
156 }
157
158 r = g_ascend_dllist;
159 while (r != NULL) {
160 if (strcmp(path,r->path)==0) {
161 ERROR_REPORTER_HERE(ASC_PROG_WARNING,"Attempt to load already loaded '%s'.",path);
162 return;
163 }
164 r = r->next;
165 }
166 }
167
168
169 /*-----------------------------------------------
170 WINDOWS
171 */
172 #if defined(__WIN32__)
173 # include <windows.h>
174
175 int Asc_DynamicLoad(CONST char *path, CONST char *initFun){
176 HINSTANCE xlib;
177 ExternalLibraryRegister_fptr_t install = NULL;
178
179 if (NULL == path) {
180 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed: Null path\n");
181 return 1;
182 }
183
184 AscCheckDuplicateLoad(path); /* whine if we've see it before */
185 /*
186 * If the named library does not exist, if it's not loadable or if
187 * it does not define the named install proc, report an error
188 */
189
190 xlib = LoadLibrary(path);
191 if (xlib == NULL) {
192 ERROR_REPORTER_HERE(ASC_PROG_ERR,"LoadLibrary failed\n'%s'",path);
193 return 1;
194 }
195 #if 0
196 ERROR_REPORTER_HERE(ASC_PROG_NOTE,"LoadLibrary succeeded, '%s'\n",path);
197 #endif
198
199 if (NULL != initFun) {
200 install = (int (*)(void))GetProcAddress(xlib,initFun);
201 if (install == NULL) {
202 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Required function '%s' not found", initFun);
203 (void)FreeLibrary(xlib);
204 return 1;
205 #if 0
206 }else{
207 FPRINTF(ASCERR,"FOUND INITFCN %s AT %d\n",initFun,install);
208 #endif
209 }
210 }
211 if (0 != AscAddRecord(xlib,path)) {
212 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed to record library (%s)\n",path);
213 }
214 return (install == NULL) ? 0 : (*install)();
215 }
216 #define ASCDL_OK /* this line should appear inside each Asc_DynamicLoad */
217
218 # define UNLOAD FreeLibrary
219 # define DLLSYM GetProcAddress
220 # define DLL_CAST (HINSTANCE)
221 # define ASC_DLERRSTRING "unknown"
222 # define UNLOAD_SUCCESS TRUE
223
224 #endif /* __WIN32__ */
225
226 /*-----------------------------------------------
227 UNIX/LINUX
228 */
229 /*
230 SOLARIS and LINUX
231 */
232 /* NOTE, added defined(__unix__) here, not sure if that's a bad thing or not -- johnpye */
233 /*
234 From a quick Google, it appears that AIX 5.1 now provides dlfcn.h,
235 so I'll remove the code that was emulating it here. -- johnpye
236 */
237 #if (defined(sun) || defined(linux) || defined(__unix__) || defined(solaris) \
238 || defined(_AIX) || defined(_SGI_SOURCE) || defined(__APPLE__))
239 # ifndef MACH
240 # include <dlfcn.h>
241 # else
242 # error "MACH unsupported"
243 # endif /* mach */
244
245 int Asc_DynamicLoad(CONST char *path, CONST char *initFun)
246 {
247 void *xlib;
248 ExternalLibraryRegister_fptr_t install = NULL;
249
250 if (NULL == path) {
251 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed: null path");
252 return 1;
253 }
254
255 AscCheckDuplicateLoad(path); /* whine if we've see it before */
256
257 /*
258 * If the named library does not exist, if it's not loadable or if
259 * it does not define the named install proc, report an error
260 */
261 xlib = dlopen(path, 1);
262 if (xlib == NULL) {
263 ERROR_REPORTER_HERE(ASC_PROG_ERR,"%s",(char *)dlerror());
264
265 #ifdef linux
266 ERROR_REPORTER_HERE(ASC_PROG_ERR,"LD_LIBRARY_PATH = \"%s\"",getenv("LD_LIBRARY_PATH"));
267 #endif
268
269 return 1;
270 }
271 if (NULL != initFun) {
272 /* https://www.opengroup.org/sophocles/show_mail.tpl?source=L&listname=austin-review-l&id=2252 */
273 *(void**)(&install) = dlsym(xlib, initFun);
274 if (install == NULL) {
275 ERROR_REPORTER_HERE(ASC_PROG_ERR,"While attempting to run '%s' in '%s': %s",initFun, path, (char *)dlerror());
276 dlclose(xlib);
277 return 1;
278 }
279 }
280
281 if (0 != AscAddRecord(xlib,path)) {
282 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed to record library (%s)",path);
283 }
284 return (install == NULL) ? 0 : (*install)();
285 }
286 #define ASCDL_OK /* this line should appear inside each Asc_DynamicLoad */
287
288 # define UNLOAD dlclose
289 # define DLLSYM dlsym
290 # define DLL_CAST (void *)
291 # define ASC_DLERRSTRING dlerror()
292 # define UNLOAD_SUCCESS 0
293
294 #endif /* posix: linux, unix, solaris,sgi */
295
296 /*-----------------------------------------------
297 HPUX
298 */
299 #ifdef __hpux
300 /*
301 Kirk Abbott last fiddled with the following, which was
302 originally put in place my Michael Moore for an
303 HP/UX 9.X Operating Sys back in 1993. Arrr. No idea if
304 it still works.
305 */
306
307 # include <dl.h>
308 # include <errno.h>
309
310 int Asc_DynamicLoad(CONST char *path, CONST char *initFun)
311 {
312 shl_t xlib;
313 ExternalLibraryRegister_fptr_t install = NULL;
314 int i;
315
316 if (NULL == path) {
317 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed: Null path");
318 return 1;
319 }
320
321 AscCheckDuplicateLoad(path); /* whine if we've see it before */
322
323 /*
324 * If the named library does not exist, if it's not loadable or if
325 * it does not define the named install proc, report an error
326 */
327 xlib = shl_load(path, BIND_IMMEDIATE | BIND_VERBOSE, 0L);
328 if (xlib == (shl_t) NULL) {
329 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to load shared library: %s",strerror(errno));
330 return 1;
331 }
332 if (NULL != initFun) {
333 i = shl_findsym(&xlib, initFun, TYPE_PROCEDURE, &install);
334 if (i == -1) {
335 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to find needed symbol '%s': %s",
336 initFun, strerror(errno));
337 shl_unload(xlib); /* baa */
338 return 1;
339 }
340 if(install == NULL) {
341 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to find needed symbol '%s'. Error type unknown",initFun);
342 shl_unload(xlib); /* baa */
343 return 1;
344 }
345 }
346 if (0 != AscAddRecord(xlib,path)) {
347 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed to record library (%s)",path);
348 }
349 return (install == NULL) ? 0 : (*install)();
350 }
351 # define ASCDL_OK /* this line should appear inside each Asc_DynamicLoad */
352
353 # define UNLOAD shl_unload
354 # define DLL_CAST (shl_t)
355 # define ASC_DLERRSTRING "NULL definition"
356 # define UNLOAD_SUCCESS 0
357
358 #endif /* __hpux */
359
360 /*-----------------------------------------------
361 Did we get something from the above?
362 */
363
364 #ifndef ASCDL_OK
365 # error "Unable to define an Asc_DynamicLoad function. Check your compiler options and installed system libraries."
366 #endif
367
368 /**-----------------------------------------------
369 DYNAMIC UNLOADING
370 */
371
372 int Asc_DynamicUnLoad(CONST char *path)
373 {
374 void *dlreturn;
375 int retval;
376
377 if (NULL == path) {
378 ERROR_REPORTER_HERE(ASC_PROG_ERR, "Failed: Null path");
379 return -3;
380 }
381
382 dlreturn = AscDeleteRecord(path);
383 if (dlreturn == NULL) {
384 ERROR_REPORTER_HERE(ASC_PROG_ERR, "Unable to remember or unload %s", path);
385 return -3;
386 }
387 CONSOLE_DEBUG("Asc_DynamicUnLoad: forgetting & unloading %s", path);
388 /*
389 * dlclose() returns 0 on success, FreeLibrary() returns TRUE.
390 * A uniform convention is preferable, so trap and return 0 on success.
391 */
392 retval = UNLOAD(DLL_CAST dlreturn);
393 return (retval == UNLOAD_SUCCESS) ? 0 : retval;
394 }
395
396 /**-----------------------------------------------
397 DYNAMIC VARIABLE LINKING
398 */
399 void *Asc_DynamicVariable(CONST char *libname, CONST char *symbol)
400 {
401 void *dlreturn;
402 void *symreturn;
403 #ifdef __hpux
404 int i;
405 #endif
406
407 if (libname == NULL) {
408 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed: Null libname");
409 return NULL;
410 }
411 if (symbol == NULL) {
412 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed: Null symbol");
413 return NULL;
414 }
415
416 dlreturn = AscFindDLRecord(libname);
417 if (dlreturn == NULL) {
418 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to find requested library %s", libname);
419 return NULL;
420 }
421 #ifdef __hpux
422 i = shl_findsym(&dlreturn, symbol, TYPE_UNDEFINED, &symreturn);
423 if (i == -1) {
424 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to find requested symbol '%s' in %s (%s)",
425 symbol, libname, strerror(errno));
426 symreturn = NULL;
427 }
428 #elif defined(__WIN32__)
429 /*
430 * Here's a bit of possibly-misdirected casting horror.
431 * ISO C forbids casting between function and data pointers, so, naturally,
432 * we cast between function and data pointers. Well, we don't have much
433 * choice. GetProcAddress() returns a function pointer for both functions
434 * and variables so we have to do the cast for variables. This is ok on
435 * 32 bit Windows since the pointers are compatible. Then, to avoid
436 * being reminded by the compiler that we're doing something illegal,
437 * we apply convoluted casting to shut it up.
438 * Oh, the crap you can find on the internet... JDS
439 */
440 *(FARPROC*)(&symreturn) = GetProcAddress((HINSTANCE)dlreturn, symbol);
441 #else
442 /* no problem on POSIX systems - dlsym() returns a void *. */
443 symreturn = dlsym(dlreturn, symbol);
444 #endif
445 if (symreturn == NULL) {
446 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to find requested symbol '%s' in %s",symbol,libname);
447 ERROR_REPORTER_NOLINE(ASC_PROG_ERR,"Error type: %s",ASC_DLERRSTRING);
448 }
449 return symreturn;
450 }
451
452 /**-----------------------------------------------
453 DYNAMIC FUNCTION LINKING
454 */
455 DynamicF Asc_DynamicFunction(CONST char *libname, CONST char *symbol)
456 {
457 void *dlreturn;
458 DynamicF symreturn;
459 #ifdef __hpux
460 int i;
461 #endif
462
463 if (libname == NULL) {
464 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed: null library name");
465 return NULL;
466 }
467 if (symbol == NULL) {
468 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed: null function name");
469 return NULL;
470 }
471
472 dlreturn = AscFindDLRecord(libname);
473 if (dlreturn == NULL) {
474 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to find requested library %s", libname);
475 return NULL;
476 }
477 #ifdef __hpux
478 i = shl_findsym(&dlreturn, symbol, TYPE_UNDEFINED, &symreturn);
479 if (i == -1) {
480 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to find requested function '%s' in %s (%s)",
481 symbol, libname, strerror(errno));
482 symreturn = NULL;
483 }
484 #elif defined(__WIN32__)
485 /* no problem on Windows - GetProcAddress() returns a function pointer. */
486 symreturn = (DynamicF)GetProcAddress((HINSTANCE)dlreturn, symbol);
487 #else
488 /*
489 * Here's the corresponding bit of possibly-misdirected casting horror.
490 * ISO C forbids casting between function and data pointers, so, naturally,
491 * we cast between function and data pointers. Well, we don't have much
492 * choice. dlsym() returns a void* for both variables and functions so we
493 * have to do the cast for functions. This is ok on POSIX systems since the
494 * pointer types are compatible. Then, to avoid being reminded by the
495 * compiler that we're doing something illegal, we apply convoluted casting
496 * to shut it up. Oh, the crap you can find on the internet... JDS
497 */
498 *(void**)(&symreturn) = dlsym(dlreturn, symbol);
499 #endif
500 if (symreturn == NULL) {
501 ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to find requested function '%s' in %s",symbol,libname);
502 ERROR_REPORTER_NOLINE(ASC_PROG_ERR,"Error type: %s",ASC_DLERRSTRING);
503 }
504 return symreturn;
505 }
506
507
508 /*-----------------------------------------------------------------------------
509 SEARCHING FOR LIBRARIES
510 */
511
512
513 /**
514 Create a library filename according to platform standard naming.
515
516 @param partialname The partial filename (eg 'mylib')
517 @return Complete filename (eg 'libmylib.so' or 'mylib.dlll', etc)
518
519 Basically just adds ASC_SHLIBPREFIX to start and ASC_SHLIBSUFFIX to end.
520
521 No allowance made for 'soname' suffixes eg 'libsomething.so.1' etc. But
522 that's probably OK as those methods aren't really applicable to dlopened
523 libraries (eg the soname symlink mechanism breaks down).
524 */
525 char *dynaload_lib_filename(const char *partialname){
526 char *buffer;
527 buffer = ASC_NEW_ARRAY(char,PATH_MAX);
528 #if !defined(ASC_SHLIBSUFFIX) || !defined(ASC_SHLIBPREFIX)
529 # error "ASC_SHLIBSUFFIX and ASC_SHLIBPREFIX are not defined"
530 #endif
531 snprintf(buffer,PATH_MAX,"%s%s%s",ASC_SHLIBPREFIX,partialname,ASC_SHLIBSUFFIX);
532 return buffer;
533 }
534
535
536 /**
537 A little structure to help with searching for libraries
538
539 @see test_librarysearch
540 */
541 struct LibrarySearch{
542 struct FilePath *partialpath;
543 char fullpath[PATH_MAX];
544 };
545
546 FilePathTestFn test_librarysearch;
547
548 /**
549 A 'test' function for passing to the ospath_searchpath_iterate function.
550 This test function will return a match when a library having the required
551 name is present in the fully resolved path.
552 */
553 int test_librarysearch(struct FilePath *path, void *userdata){
554 /* user data = the relative path, plus a place
555 to store the full path when found */
556 FILE *f;
557 struct LibrarySearch *ls;
558 struct FilePath *fp;
559
560 ls = (struct LibrarySearch *)userdata;
561 fp = ospath_concat(path,ls->partialpath);
562 if(fp==NULL){
563 char *tmp;
564 tmp = ospath_str(path);
565 CONSOLE_DEBUG("Unable to concatenate '%s'...",tmp);
566 ospath_free_str(tmp);
567 tmp = ospath_str(ls->partialpath);
568 CONSOLE_DEBUG("... and '%s'...",tmp);
569 ospath_free_str(tmp);
570 return 0;
571 }
572
573 ospath_strncpy(fp,ls->fullpath,PATH_MAX);
574 /* CONSOLE_DEBUG("SEARCHING FOR %s",ls->fullpath); */
575
576 f = ospath_fopen(fp,"r");
577 if(f==NULL){
578 ospath_free(fp);
579 return 0;
580 }
581 fclose(f);
582
583 /* ERROR_REPORTER_HERE(ASC_PROG_NOTE,"FOUND! %s\n",ls->fullpath); */
584 ospath_free(fp);
585 return 1;
586 }
587
588 /**
589 @DEPRECATED this function needs to be rewritten to use 'ImportHandler'
590 functionality.
591 */
592 char *SearchArchiveLibraryPath(CONST char *name, char *dpath, const char *envv){
593 struct FilePath *fp1, *fp2, *fp3; /* relative path */
594 char *s1;
595 char *buffer;
596
597 struct LibrarySearch ls;
598 struct FilePath **sp;
599 char *path, *foundpath;
600 ospath_stat_t buf;
601 FILE *f;
602
603 fp1 = ospath_new_noclean(name);
604 if(fp1==NULL){
605 ERROR_REPORTER_HERE(ASC_USER_ERROR,"Invalid partial path '%s'",name);
606 ospath_free(fp1);
607 return NULL;
608 }
609
610 s1 = ospath_getfilestem(fp1);
611 if(s1==NULL){
612 /* not a file, so fail... */
613 return NULL;
614 }
615
616 fp2 = ospath_getdir(fp1);
617 if(fp2==NULL){
618 ERROR_REPORTER_HERE(ASC_PROG_ERR,"unable to retrieve file dir");
619 return NULL;
620 }
621
622 buffer = dynaload_lib_filename(s1);
623
624 fp3 = ospath_new(buffer);
625 ASC_FREE(buffer);
626 ospath_free(fp1);
627 fp1 = ospath_concat(fp2,fp3);
628 ospath_free(fp2);
629 ospath_free(fp3);
630 ospath_free_str(s1);
631
632 /* attempt to open "name" directly */
633 if(0==ospath_stat(fp1,&buf) && NULL!=(f = ospath_fopen(fp1,"r")) ){
634 char *tmp;
635 tmp = ospath_str(fp1);
636 CONSOLE_DEBUG("Library '%s' opened directly, without path search",tmp);
637 ospath_free_str(tmp);
638 fp2 = ospath_getabs(fp1);
639 foundpath = ospath_str(fp2);
640 ospath_free(fp2);
641 fclose(f);
642 }else{
643
644 ls.partialpath = fp1;
645
646 path=Asc_GetEnv(envv);
647 if(path==NULL){
648 /* CONSOLE_DEBUG("Library search path env var '%s' not found, using default path '%s'",envv,dpath); */
649 path=dpath;
650 }
651
652 /* CONSOLE_DEBUG("SEARCHPATH IS %s",path); */
653 sp = ospath_searchpath_new(path);
654
655 if(NULL==ospath_searchpath_iterate(sp,&test_librarysearch,&ls)){
656 ospath_free(fp1);
657 ospath_searchpath_free(sp);
658 return NULL;
659 }
660
661 foundpath = ASC_NEW_ARRAY(char,strlen(ls.fullpath)+1);
662 strcpy(foundpath,ls.fullpath);
663 ospath_searchpath_free(sp);
664 }
665
666 ospath_free(fp1);
667 return foundpath;
668 }

john.pye@anu.edu.au
ViewVC Help
Powered by ViewVC 1.1.22