home *** CD-ROM | disk | FTP | other *** search
/ linuxmafia.com 2016 / linuxmafia.com.tar / linuxmafia.com / pub / palmos / development / shlib.txt < prev    next >
Text File  |  2000-12-07  |  23KB  |  583 lines

  1. Shared libraries on the Palm Pilot
  2.  
  3. After much playing around with the Palm Pilot simulator and debugger, I have
  4. figured out how to use PalmOS's shared library features. Note that this is
  5. not "official" in any way, but it seems to work for me. Comments are
  6. welcome. This document is divided into four sections:
  7.  
  8.    * Intro to shared libraries on the Palm Pilot
  9.    * How to use shared libraries
  10.    * How to make your own shared libraries
  11.    * Conclusions
  12.  
  13. Note that I have only experimented with the Palm Pilot Pro; I have not tried
  14. the Palm Pilot Personal, and the old Pilots with PalmOS 1 almost certainly
  15. won't work (they don't have the SysLibLoad function, but I guess someone
  16. could emulate it...).
  17.  
  18. Intro to shared libraries on the Palm Pilot
  19.  
  20. Shared libraries (sometimes called "system libraries") on the Palm Pilot
  21. enable many applications to share common code, without having to have a copy
  22. of the code in each application's code resource. For example, there can be
  23. one copy of encryption routines, or a version of the standard C library, and
  24. many applications can be using it.
  25.  
  26. Shared libraries can also help you get around the 32K code size limit; you
  27. can break up your code into a main portion (of at most 32K), and a number of
  28. libraries (each of at most 32K).
  29.  
  30. Shared libraries are implemented as resource databases, with a resource type
  31. of libr and a resource ID of 0. In contrast, an application is a resource
  32. database with (usually) four resources: code 0, code 1, data 0, and pref 0.
  33. The libr 0 resource in a shared library corresponds exactly to the code 1
  34. resource in an application.
  35.  
  36. A shared library on the Palm Pilot is not like one on most varieties of
  37. Unix. Whereas for Unix, shared libraries export a symbol table, and the
  38. operating system's dynamic loader maps calls based on their symbol name, for
  39. the Palm Pilot, shared libraries export a dispatch table. A dispatch table
  40. is basically a list of addresses of the functions in the library that can be
  41. called from other applications.
  42.  
  43. Every shared library must include the following four functions as the first
  44. four entries in its dispatch table:
  45.  
  46. open
  47.      Applications call this to initialize the library.
  48. close
  49.      Applications call this to free storage allocated by the library, prior
  50.      to exiting.
  51. sleep
  52.      The system will call this routine for each shared library that is in
  53.      use when the system is about to power down.
  54. wake
  55.      The system will call this routine for each shared library that is in
  56.      use when the system has just powered up.
  57.  
  58. These functions do not need to have these exact names; the names used
  59. internally by the shared library have no relation to the names applications
  60. use to access the functions. For example, the open routine in the Serial
  61. Library is called SerOpen, and the one in the Network Library is called
  62. NetLibOpen.
  63.  
  64. The remainder of the functions in the dispatch table can do whatever the
  65. library designer wants. I'm not sure what the size limit of the dispatch
  66. table is, but I haven't hit it yet (it's at most about 5500 functions, but
  67. it could be less).
  68.  
  69. How to use shared libraries
  70.  
  71. The Palm Pilot Pro, as shipped, comes with two libraries pre-installed: the
  72. serial library and the networking library. This section explains how to
  73. install third-party shared libraries, and how to write programs that use
  74. them.
  75.  
  76. To install a shared library, simply HotSync (or use something like
  77. pilot-xfer) the .prc file to your Pilot. In my experience, if you already
  78. have a copy (or an older version) of a library on your Palm Pilot, you need
  79. to delete it (with the Memory application) before installing the new
  80. version. I'm not sure why that is, and I'll look into it more later.
  81.  
  82. Once the library is installed, applications can use it. In order for a
  83. shared library to be used, it needs to be entered into the loaded library
  84. table. Once that has been done, its index in the loaded library table is
  85. called its reference number.
  86.  
  87. An application that wishes to use a shared library should use the SysLibFind
  88. function. This function looks up a shared library in the loaded library
  89. table, and gives back the reference number.
  90.  
  91. If SysLibFind fails, it means the library is not currently loaded. The
  92. application can try to load the library itself by using the SysLibLoad
  93. function. Once a reference number for the library has been obtained, the
  94. library's open function should be called. These functions can often be
  95. called from the StartApplication routine.
  96.  
  97. In the example below, the application is trying to use the library called
  98. "Gauss Library", which is stored in a resource database of type 'libr' and
  99. creator 'Gaus':
  100.  
  101.     Err err;
  102.     UInt GaussLibRefNum;
  103.  
  104.     ...
  105.  
  106.     err = SysLibFind("Gauss Library", &GaussLibRefNum);
  107.     if (err) err = SysLibLoad('libr', 'Gaus', &GaussLibRefNum);
  108.     ErrFatalDisplayIf(err, "Cannot load Gauss Library!");
  109.     err = GaussLibOpen(GaussLibRefNum);
  110.     /* Check for errors in the Open() routine */
  111.  
  112. Note that SysLibLoad is not defined in some header file distributions. If
  113. you get an error regarding this, put the following near the top of your C
  114. file:
  115.  
  116.     Err SysLibLoad(ULong type, ULong creator, UIntPtr refNumPtr)
  117.         SYS_TRAP(684);
  118.  
  119. Every function in a shared library must take a UInt refNum as its first
  120. argument. For example, if the above Gauss Library implements arithmetic on
  121. Gaussian (complex) integers, and it contains a function Err GaussLibAdd(UInt
  122. refNum, Gauss_t *sum, Gauss_t *a, Gauss_t *b), the application would call it
  123. as follows:
  124.  
  125.     Gauss_t a, b, sum;
  126.     Err err;
  127.  
  128.     ...
  129.  
  130.     err = GaussLibAdd(GaussLibRefNum, &sum, &a, &b);
  131.  
  132. The application would also have #include "GaussLib.h" in it (this header
  133. file would generally be distributed with the library). The GaussLib.h header
  134. file would have definitions for any data types used by the library, as well
  135. as declarations for the library functions. For example, the declarations in
  136. GaussLib.h might be:
  137.  
  138.     Err GaussLibOpen(UInt refNum) SYS_TRAP(sysLibOpen);
  139.     Err GaussLibClose(UInt refNum, UIntPtr numappsP) SYS_TRAP(sysLibClose);
  140.     Err GaussLibSleep(UInt refNum) SYS_TRAP(sysLibSleep);
  141.     Err GaussLibWake(UInt refNum) SYS_TRAP(sysLibWake);
  142.     Err GaussLibCreate(UInt refNum, Gauss_t *val, Long re, Long im)
  143.         SYS_TRAP(sysLibCustom);
  144.     Err GaussLibRead(UInt refNum, Gauss_t *val, Long *re, Long *im)
  145.         SYS_TRAP(sysLibCustom+1);
  146.     Err GaussLibAdd(UInt refNum, Gauss_t *sum, Gauss_t *a, Gauss_t *b)
  147.         SYS_TRAP(sysLibCustom+2);
  148.     Err GaussLibMul(UInt refNum, Gauss_t *prod, Gauss_t *a, Gauss_t *b)
  149.         SYS_TRAP(sysLibCustom+3);
  150.  
  151. These declarations would enable applications to call, in addition to the
  152. four "standard" functions, the functions GaussLibCreate, GaussLibRead,
  153. GaussLibAdd, and GaussLibMul.
  154.  
  155. Finally, when the application is about to exit, it should close the shared
  156. library by calling its close function. This is often done in the
  157. StopApplication routine. In addition, an application may choose to unload
  158. the library from the loaded library table. It should only do this, however,
  159. if the library is not in use by other applications (the close() function in
  160. the library could return information about whether any other applications
  161. are using the library). Libraries are unloaded by calling the SysLibRemove
  162. function (after which the reference number is invalid). For example,
  163.  
  164.     UInt numapps;
  165.  
  166.     err = GaussLibClose(GaussLibRefNum, &numapps);
  167.     /* Check for errors in the Close() routine */
  168.     if (numapps == 0) {
  169.         SysLibRemove(GaussLibRefNum);
  170.     }
  171.  
  172. How to make your own shared libraries
  173.  
  174. This section will outline how to create a shared library using the Unix
  175. tools. A shared library is created in the same way as a regular Palm Pilot
  176. application, with a couple of notable exceptions:
  177.  
  178.    * The library can have no data or bss segment. This means no global or
  179.      static variables. Static constants (including literal strings) are
  180.      fine, though, as these go in the text (code) segment.
  181.    * The m68k executable is linked with the -shared flag to gcc, which
  182.      prevents the C run-time (crt0) and standard C libraries from being
  183.      automatically included. Instead of the C run-time, the first object
  184.      file in the link command must be the one that begins with your
  185.      library's dispatch table. More on this later.
  186.  
  187. We will now see how to create the Gauss Library, which was described above.
  188. Here is the complete GaussLib.h:
  189.  
  190.     #ifndef __GAUSSLIB_H__
  191.     #define __GAUSSLIB_H__
  192.  
  193.     /* Data structures */
  194.     typedef struct {
  195.         Long re, im;
  196.     } Gauss_t;
  197.  
  198.     /* Function declarations */
  199.     Err GaussLibOpen(UInt refNum) SYS_TRAP(sysLibTrapOpen);
  200.     Err GaussLibClose(UInt refNum, UIntPtr numappsP) SYS_TRAP(sysLibTrapClose);
  201.     Err GaussLibSleep(UInt refNum) SYS_TRAP(sysLibTrapSleep);
  202.     Err GaussLibWake(UInt refNum) SYS_TRAP(sysLibTrapWake);
  203.     Err GaussLibCreate(UInt refNum, Gauss_t *val, Long re, Long im)
  204.         SYS_TRAP(sysLibTrapCustom);
  205.     Err GaussLibRead(UInt refNum, Gauss_t *val, Long *re, Long *im)
  206.         SYS_TRAP(sysLibTrapCustom+1);
  207.     Err GaussLibAdd(UInt refNum, Gauss_t *sum, Gauss_t *a, Gauss_t *b)
  208.         SYS_TRAP(sysLibTrapCustom+2);
  209.     Err GaussLibMul(UInt refNum, Gauss_t *prod, Gauss_t *a, Gauss_t *b)
  210.         SYS_TRAP(sysLibTrapCustom+3);
  211.  
  212.     #endif
  213.  
  214. The format of this header file is simple; the first part defines data
  215. structures, and the second declares the library functions. Note that each
  216. library function declaration is followed by a SYS_TRAP attribute. The four
  217. standard functions should have the values listed above, and subsequent
  218. functions should have arguments to SYS_TRAP starting at sysLibTrapCustom and
  219. incrementing from there.
  220.  
  221. We now start looking at GaussLib.c. The first section of the C file sets up
  222. declarations for functions that the dispatch table will use, as well as
  223. defining the globals structure for the library. Remember that since the
  224. library can have no global variables, it needs to dynamically allocate space
  225. for its globals. It does this by having a single structure which contains
  226. all of its globals, and it allocates memory for this structure in the Open
  227. function, and frees it in the Close function. The only global variable we
  228. will use in the Gauss Library is a reference count of the number of
  229. applications currently using the library. The beginning of GaussLib.c, then,
  230. looks like:
  231.  
  232.     #include <SysAll.h>
  233.     #include "GaussLib.h"
  234.  
  235.     /* Dispatch table declarations */
  236.     ULong install_dispatcher(UInt,SysLibTblEntryPtr);
  237.     Ptr *gettable(void);
  238.  
  239.     /* The globals structure for this library */
  240.     typedef struct {
  241.         UInt refcount;
  242.     } GaussLib_globals;
  243.  
  244. The first piece of code in GaussLib.c must be the code which sets up the
  245. dispatch table. For any library, it looks like this:
  246.  
  247.     ULong start (UInt refnum, SysLibTblEntryPtr entryP)
  248.     {
  249.         ULong ret;
  250.  
  251.         asm("
  252.         movel %%fp@(10),%%sp@-
  253.         movew %%fp@(8),%%sp@-
  254.         jsr install_dispatcher(%%pc)
  255.         movel %%d0,%0
  256.         jmp out(%%pc)
  257.     gettable:
  258.         lea jmptable(%%pc),%%a0
  259.         rts
  260.     jmptable:
  261.         dc.w 50
  262.         dc.w 18
  263.         dc.w 22
  264.         dc.w 26
  265.         dc.w 30
  266.         dc.w 34
  267.         dc.w 38
  268.         dc.w 42
  269.         dc.w 46
  270.         jmp gausslib_open(%%pc)
  271.         jmp gausslib_close(%%pc)
  272.         jmp gausslib_sleep(%%pc)
  273.         jmp gausslib_wake(%%pc)
  274.         jmp gausslib_create(%%pc)
  275.         jmp gausslib_read(%%pc)
  276.         jmp gausslib_add(%%pc)
  277.         jmp gausslib_mul(%%pc)
  278.         .asciz \"Gauss Library\"
  279.     .even
  280.     out:
  281.         " : "=r" (ret) :);
  282.         return ret;
  283.     }
  284.  
  285.     ULong install_dispatcher(UInt refnum, SysLibTblEntryPtr entryP)
  286.     {
  287.         Ptr *table = gettable();
  288.         entryP->dispatchTblP = table;
  289.         entryP->globalsP = NULL;
  290.         return 0;
  291.     }
  292.  
  293. The only part of the above code that will change for different libraries is
  294. the section between the jmptable: and out: labels. Here's how to work it
  295. out: suppose your library has n functions, including the four standard ones
  296. (for GaussLib, n is 8). Then on the first line, you would put dc.w 6n+2
  297. (here, 6n+2 = 50).
  298.  
  299. The next n lines are of the form dc.w 2n+4i-2, as i ranges from 1 to n.
  300.  
  301. The next n lines are of the form jmp internal_function_name(%%pc), where
  302. each internal_function_name is the name of one of the library functions, as
  303. named in the source code for the library, which may be different from the
  304. name in GaussLib.h. (In fact, if it has the same name, you may run into
  305. problems unless you change the definition of SYS_TRAP.) The first four
  306. should be the standard four functions, and the remaining ones must be in the
  307. same order as they are listed in the header file.
  308.  
  309. The last line contains the textual name of the library, which applications
  310. will use in a SysLibFind call.
  311.  
  312. Now you should define the four standard functions. The open function should,
  313. if necessary, allocate memory for the library globals. Note that the amount
  314. of dynamic memory available is limited. If you have a large amount of
  315. constant data (such as a word list), include it as static const data in your
  316. program, so that it ends up in the text segment. If you really need a large
  317. amount of variable data, consider putting it into a database, and only
  318. including a pointer (or handle) to the database in the globals structure.
  319. Here is a template for the open routine:
  320.  
  321.     Err gausslib_open(UInt refnum)
  322.     {
  323.         /* Get the current globals value */
  324.         SysLibTblEntryPtr entryP = SysLibTblEntry(refnum);
  325.         GaussLib_globals *gl = (GaussLib_globals*)entryP->globalsP;
  326.  
  327.         if (gl) {
  328.             /* We are already open in some other application; just
  329.                increment the refcount */
  330.             gl->refcount++;
  331.             return 0;
  332.         }
  333.  
  334.         /* We need to allocate space for the globals */
  335.         entryP->globalsP = MemPtrNew(sizeof(GaussLib_globals));
  336.         MemPtrSetOwner(entryP->globalsP, 0);
  337.         gl = (GaussLib_globals*)entryP->globalsP;
  338.  
  339.         /* Initialize the globals */
  340.         gl->refcount = 1;
  341.  
  342.         return 0;
  343.     }
  344.  
  345. The first two lines of code are the idiom that any library function would
  346. use if it needed to access some value in the globals structure. The next
  347. section deals with the case that some other application has already opened
  348. this library. In our case, we just increment the reference count, but other
  349. libraries may want to return an error code.
  350.  
  351. The section after that is the idiom for allocating the memory for a globals
  352. structure, and the last section is the library-specific initialization of
  353. the global variables.
  354.  
  355. The close routine should free any memory or close any databases allocated or
  356. opened by the open routine, but only if no other application currently has
  357. the library open. Here is a template for the close routine:
  358.  
  359.     Err gausslib_close(UInt refnum, UIntPtr numappsP)
  360.     {
  361.         /* Get the current globals value */
  362.         SysLibTblEntryPtr entryP = SysLibTblEntry(refnum);
  363.         GaussLib_globals *gl = (GaussLib_globals*)entryP->globalsP;
  364.         if (!gl) {
  365.             /* We're not open! */
  366.             return 1;
  367.         }
  368.  
  369.         /* Clean up */
  370.         *numappsP = --gl->refount;
  371.         if (*numappsP == 0) {
  372.             MemChunkFree(entryP->globalsP);
  373.             entryP->globalsP = NULL;
  374.         }
  375.  
  376.         return 0;
  377.     }
  378.  
  379. This routine first gets a pointer to the library's global data. If this is
  380. NULL, the library is not open. This error can be signalled to the
  381. application with a result code, as above, or the library can do something
  382. like ErrFatalDisplayIf(!gl, "Gauss Library is not open!"); instead.
  383.  
  384. The remainder of this routine should be used to deallocate the library
  385. global memory, if the library reference count has dropped to 0. In this case
  386. (which is a good idea), we also return the number of applications that still
  387. have the library open. If this is 0, the application that just closed us
  388. should remove the library from the loaded library table.
  389.  
  390. The sleep and wake routines are usually trivial; only if your library
  391. actually cares (as, say, the Network Library might) when the Palm Pilot is
  392. turned on or off should anything happen here.
  393.  
  394.     Err gausslib_sleep(UInt refnum)
  395.     {
  396.         return 0;
  397.     }
  398.  
  399.     Err gausslib_wake(UInt refnum)
  400.     {
  401.         return 0;
  402.     }
  403.  
  404. From here on in, you just write the code to implement your library
  405. functions. In our example, none of our functions access the library globals,
  406. so it is very easy:
  407.  
  408.     Err gausslib_create(UInt refNum, Gauss_t *val, Long re, Long im)
  409.     {
  410.         val->re = re;
  411.         val->im = im;
  412.         return 0;
  413.     }
  414.  
  415.     Err gausslib_read(UInt refNum, Gauss_t *val, Long *re, Long *im)
  416.     {
  417.         *re = val->re;
  418.         *im = val->im;
  419.         return 0;
  420.     }
  421.  
  422.     Err gausslib_add(UInt refNum, Gauss_t *sum, Gauss_t *a, Gauss_t *b)
  423.     {
  424.         sum->re = a->re + b->re;
  425.         sum->im = a->im + b->im;
  426.         return 0;
  427.     }
  428.  
  429.     Err gausslib_mul(UInt refNum, Gauss_t *prod, Gauss_t *a, Gauss_t *b)
  430.     {
  431.         prod->re = (a->re*b->re)-(a->im*b->im);
  432.         prod->im = (a->re*b->im)+(a->im*b->re);
  433.         return 0;
  434.     }
  435.  
  436. We now try to compile GaussLib.c into a shared library resource database.
  437. The first step is to compile each C file into an object file. In our case,
  438. we only have one C file:
  439.  
  440.     m68k-palmos-coff-gcc -O5 -c GaussLib.c
  441.  
  442. We now link all of our object files into a program file. If you have more
  443. than one object file, the one that starts with the dispatch table must come
  444. first. Some libraries may need the -lgcc at the end of this line, and some
  445. may not; try it without, and if you get errors, try it with.
  446.  
  447.     m68k-palmos-coff-gcc -shared -o GaussLib GaussLib.o -lgcc
  448.  
  449. Now, make sure your data and bss segments are empty:
  450.  
  451.     m68k-palmos-coff-objdump --section-headers GaussLib
  452.  
  453.     GaussLib:     file format coff-m68k
  454.  
  455.     Sections:
  456.     Idx Name          Size      VMA       LMA       File off  Algn
  457.       0 .text         00000224  00010000  00010000  00002000  2**2
  458.                       CONTENTS, ALLOC, LOAD, CODE
  459.       1 .data         00000000  00000000  00000000  00000000  2**2
  460.                       ALLOC, LOAD, DATA
  461.       2 .bss          00000000  00000000  00000000  00000000  2**2
  462.                       ALLOC
  463.  
  464. If the "Size" field of the data or bss if not 0, check your library
  465. carefully for global or static data, and either make them static constants
  466. (if possible), or else move them to the library globals structure.
  467.  
  468. Next, extract the text segment from the program file, delete the data and
  469. other non-useful segments, and rename the text segment to the library
  470. segment:
  471.  
  472.     m68k-palmos-coff-obj-res GaussLib
  473.     rm -f {code,pref,data}0000.GaussLib.grc
  474.     mv code0001.GaussLib.grc libr0000.GaussLib.grc
  475.  
  476. Now build a resource database (prc) file, containing only the library
  477. segment:
  478.  
  479.     build-prc GaussLib.prc GaussLib Gaus libr0000.GaussLib.grc
  480.  
  481. At this point, everything is ready to go, except for the fact that build-prc
  482. assumes the database it creates is of type 'appl'. Shared libraries should
  483. be of type 'libr'. It would be good if a future version of build-prc would
  484. have an option for this, but for now, here is appl2libr.c:
  485.  
  486.     #include <stdio.h>
  487.  
  488.     int main(int argc, char **argv)
  489.     {
  490.         FILE *prc;
  491.         char kind[5];
  492.  
  493.         if (argc < 2) {
  494.             fprintf(stderr, "Usage: %s file.prc\n", argv[0]);
  495.             exit(1);
  496.         }
  497.  
  498.         prc = fopen(argv[1], "r+");
  499.         if (!prc) { perror("fopen"); exit(2); }
  500.         if (fseek(prc, 0x3c, SEEK_SET)) { perror("fseek"); exit(2); }
  501.         kind[4] = '\0';
  502.         if (fread(kind, 4, 1, prc) != 1) { perror("fread"); exit(2); }
  503.         if (strcmp(kind, "appl")) {
  504.             fprintf(stderr, "%s is not an appl file\n", argv[1]);
  505.             exit(3);
  506.         }
  507.         if (fseek(prc, 0x3c, SEEK_SET)) { perror("fseek"); exit(2); }
  508.         strcpy(kind, "libr");
  509.         if (fwrite(kind, 4, 1, prc) != 1) { perror("fwrite"); exit(2); }
  510.         if (fclose(prc)) { perror("fclose"); exit(2); }
  511.         return 0;
  512.     }
  513.  
  514. Compile this program, and convert your prc file to have type 'libr':
  515.  
  516.     gcc -o appl2libr appl2libr.c
  517.     ./appl2libr GaussLib.prc
  518.  
  519. You're done! GaussLib.prc is now a shared library, ready to be uploaded to a
  520. Palm Pilot. You can distribute the prc file along with applications that use
  521. it, and/or you can distribute the header file so people can write their own
  522. applications that use the library.
  523.  
  524. For reference, here is a Makefile:
  525.  
  526.     LIBNAME = GaussLib
  527.     CREATOR = Gaus
  528.  
  529.     CC = m68k-palmos-coff-gcc
  530.     XTRALIBS = -lgcc
  531.     OBJRES = m68k-palmos-coff-obj-res
  532.     BUILDPRC = build-prc
  533.  
  534.     $(LIBNAME).prc: libr0000.$(LIBNAME).grc ./appl2libr
  535.             $(BUILDPRC) $@ $(LIBNAME) $(CREATOR) libr0000.$(LIBNAME).grc
  536.             ./appl2libr $@
  537.  
  538.     ./appl2libr: appl2libr.c
  539.             gcc -o $@ $<
  540.  
  541.     libr0000.$(LIBNAME).grc: $(LIBNAME)
  542.             $(OBJRES) $(LIBNAME)
  543.             rm -f {code,pref,data}0000.$(LIBNAME).grc
  544.             mv code0001.$(LIBNAME).grc libr0000.$(LIBNAME).grc
  545.  
  546.     $(LIBNAME): $(LIBNAME).o
  547.             $(CC) -shared -o $(LIBNAME) $(LIBNAME).o $(XTRALIBS)
  548.  
  549.     $(LIBNAME).o: $(LIBNAME).c
  550.             $(CC) -O5 -c $(LIBNAME).c
  551.  
  552.     clean:
  553.             rm -f libr0000.$(LIBNAME).grc $(LIBNAME).o
  554.  
  555.     spotless: clean
  556.             rm -f appl2libr $(LIBNAME).prc $(LIBNAME)
  557.  
  558. Conclusions
  559.  
  560. Success and failure reports, comments, and questions are welcome. Depending
  561. on the content, appropriate fora are the pilot.programmer and
  562. pilot.programmer.gcc newsgroups hosted on news.massena.com, and the
  563. pilot-unix mailing list at <pilot-unix@lists.best.com>. If necessary, I can
  564. be reached directly at the address below (be warned that my email queue
  565. sometimes gets quite backlogged).
  566.  
  567. Back to the ISAAC Group's Pilot page
  568.   ------------------------------------------------------------------------
  569.  
  570. IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
  571. DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
  572. OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF,
  573. EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  574.  
  575. THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
  576. INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
  577. FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS
  578. PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO
  579. OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
  580. MODIFICATIONS.
  581.  
  582. Ian Goldberg, iang@cs.berkeley.edu
  583.