source: project/wiki/eggref/4/9p @ 14463

Last change on this file since 14463 was 14463, checked in by sjamaan, 11 years ago

Add note about change in 9p

File size: 26.7 KB
Line 
1[[tags: egg]]
2
3== 9p
4
5[[toc:]]
6
7=== Description
8
9A pure Scheme implementation of the [[http://9p.cat-v.org|9p networked filesystem protocol]].
10
11=== Author
12
13[[Peter Bex]]
14
15=== Requirements
16
17Requires the [[iset]] egg.
18
19=== Documentation
20
219p is an implementation of the networked file system protocol known as 9P, specifically version 9P2000 which is also known as "Styx".
22This protocol is used by the [[http://cm.bell-labs.com/plan9|Plan 9 operating system]] and the [[http://wmii.suckless.org|wmii window manager]], among others.
23
24This implementation includes a low-level implementation of the protocol that is suitable both for writing clients and servers and a high-level client implementation.  There currently are no concrete plans for a high-level server implementation, but contributions are of course very welcome :)
25
26The low-level implementation is documented below under [[#9p-lolevel|9p-lolevel]] and the high-level client implementation under [[#9p-client|9p-client]].  The high-level client is discussed first, because this is the one you will most likely need.
27
28=== 9p-client
29
30The basic library was modeled after Chicken's [[/man/4/Unit posix|Unit posix]] and a few choice other procedures that interact with the filesystem.  Most procedures from Unit posix are available under the same name.  When you include the module together with posix, don't forget to prefix either these procedures or those of posix! Where possible, the procedure's signature has been unmodified, except for an additional leading argument that specifies the connection with the 9p server.
31
32==== Usage
33
34  (use 9p-client)
35  (use utf8)
36
37It is highly recommended you require utf8 in your applications, as 9p is a utf8-aware protocol.  It is not a dependency of this egg because in some situations you might decide it's safe to leave it out, for performance or memory footprint reasons.
38
39==== Connection management
40
41===== client-connect
42
43Before doing anything else, you must establish a connection with the server.  This is done with the {{client-connect}} procedure.
44
45<procedure>(client-connect inport outport [user] [mountpoint])</procedure>
46
47The {{inport}} and {{outport}} arguments are the ports you use to communicate to the server.  The {{user}} argument is the name of the user that creates the files.  It defaults to the empty string.  There is no support for authentication, so the user name is simply used for newly created files on servers that support usernames (wmii doesn't, for example).  The {{mountpoint}} also defaults to the empty string, which selects the "default mount point" on the server.  If the server has multiple mountpoints it exports, you can select with this argument.
48
49The procedure returns a connection object you must keep and use in all subsequent 9p procedure calls.
50
51You can use the following procedures to obtain some more information on the connection:
52
53<procedure>(connection-outport connection)</procedure>
54<procedure>(connection-inport connection)</procedure>
55
56Get back the underlying ports you passed to {{client-connect}}.
57
58<procedure>(connection-message-size connection)</procedure>
59
60The maximum size of a low-level message as negotiated in the connection handshake.  Not very useful unless you would like to write some custom messages.  This ''includes'' the size of the tag (2 bytes) and the message type (1 byte).
61
62
63===== client-disconnect
64
65<procedure>(client-disconnect connection)</procedure>
66
67Disconnect from the server described by {{connection}}.  This clunks any fids that are still open (in Unix terms: closes any open file descriptors).
68
69===== connection?
70
71<procedure>(connection? object)</procedure>
72
73You can verify an object is a connection to a 9p server with this predicate.
74
75==== Files as ports
76
77===== with-output-to-file
78
79<procedure>(with-output-to-file connection file thunk)</procedure>
80
81Open {{file}} on the 9p connection {{connection}} and call {{thunk}} with the {{current-output-port}} set to a port that writes to the file. When the thunk finishes, the port is closed.
82
83===== call-with-output-file
84
85<procedure>(call-with-output-file connection file procedure)</procedure>
86
87Open {{file}} on the 9p connection {{connection}} and call {{procedure}} with an output-port that corresponds to the file. When the procedure finishes, the port is closed.  Procedure should accept one argument, the output-port.
88
89===== open-output-file
90
91<procedure>(open-output-file connection file [mode])</procedure>
92
93Create an output port that will write to the given {{file}} on the 9p connection {{connection}}.  If the file exists, it is truncated.  If it does not exist yet it will be created.  If the optional {{mode}} is given, it determines with what permissions the file will be created, if it is a new file.  See [[#Permission bits|below]] for the list of file permissions.
94
95Don't forget to close the output port (with {{close-output-port}}) when you finish writing to it!
96
97===== with-input-from-file
98
99<procedure>(with-input-from-file connection file thunk)</procedure>
100
101Open {{file}} on the 9p connection {{connection}} and call {{thunk}} with the {{current-input-port}} set to a port that reads from the file. When the thunk finishes, the port is closed.
102
103===== call-with-input-file
104
105<procedure>(call-with-input-file connection file procedure)</procedure>
106
107Open {{file}} on the 9p connection {{connection}} and call {{procedure}} with an input-port that corresponds to the file. When the procedure finishes, the port is closed.  Procedure should accept one argument, the input-port.
108
109===== open-input-file
110
111<procedure>(open-input-file connection file)</procedure>
112
113Create an input port that will read from the given {{file}} on the 9p connection {{connection}}.
114
115Don't forget to close the input port (with {{close-input-port}} when you finish reading from it!
116
117==== Directories
118
119===== directory?
120
121<procedure>(directory? connection path)</procedure>
122
123Returns {{#t}} if the given {{path}} on the {{connection}} is a directory, {{#f}} if not.
124
125===== create-directory
126
127<procedure>(create-directory connection path permissions)</procedure>
128
129Create a directory on the {{connection}} with the given {{path}}.  It will have the specified {{permissions}}, see [[#Permission bits|below]] for the available permissions.
130
131===== directory
132
133<procedure>(directory connection directory [show-dotfiles?])</procedure>
134
135Returns a list with the contents of the {{directory}} on the {{connection}}. Files beginning with {{.}} are included only if {{show-dotfiles?}} is given and not #f.
136
137==== Files
138
139===== regular-file?
140
141<procedure>(regular-file? connection path)</procedure>
142
143Returns {{#t}} if the given {{path}} on the {{connection}} is a regular file, {{#f}} if not.  9p does not support symlinks or FIFOs, so this is the same as {{(not (directory? connection path))}}, even if the underlying FS is a Unix FS (the 9p egg currently does not (and probably will never) support [[http://v9fs.sourceforge.net/rfc/9p2000.u.html|9P2000.u]]).
144
145===== delete-file
146
147<procedure>(delete-file connection path)</procedure>
148
149Delete the file indicated by {{path}} on the {{connection}}. If the file does not exist or you do not have permission to delete it, an error is signaled.
150
151===== file-stat
152
153<procedure>(file-stat connection path)</procedure>
154
155Returns a 9-element vector which contains information about the file indicated by {{path}} on the {{connection}}.  It has the following contents:
156
157* The QID of the file (The qid can be queried with the [[#QIDs|QID procedures]] from 9p-lolevel.
158* The permission mode (See [[#Permission bits|the permission bits section]] for a description)
159* The access time of the file.  This is an integer which indicates the server-time when the file was last accessed.  There is no way to determine what the server's time is using the 9p protocol, so you can only use this for comparing timestamps of files on the same server unless you use an additional protocol to find out about the server's current time and zone.
160* The modification time of the file.  This is an integer which indicates the server-time when the file was last modified.
161* The file's size in bytes.
162* The filename of the file.
163* The user who owns the file (a string, not a uid, because Plan9 has only user and group names, not numerical ids).
164* The group who owns the file (a string)
165* The user who last modified the file (a string)
166
167===== file-permissions
168
169<procedure>(file-permissions connection path)</procedure>
170
171Returns the permissions of the file indicated by {{path}} on the {{connection}}. See [[#Permission bits|the permission bits section]] for a description of the possible bits.
172
173===== file-access-time
174
175<procedure>(file-access-time connection path)</procedure>
176
177Returns the access time of the file indicated by {{path}} on the {{connection}}.  See the notes under [[#file-stat]].
178
179
180===== file-modification-time
181
182<procedure>(file-modification-time connection path)</procedure>
183
184Returns the modification time of the file indicated by {{path}} on the {{connection}}.  See the notes under [[#file-stat]].
185
186===== file-size
187
188<procedure>(file-size connection path)</procedure>
189
190Returns the size, in bytes, of the file indicated by {{path}} on the {{connection}}.
191
192===== file-owner
193
194<procedure>(file-owner connection path)</procedure>
195
196Returns the name of the owner, as a string, of the file indicated by {{path}} on the {{connection}}.
197
198===== file-group
199
200<procedure>(file-group connection path)</procedure>
201
202Returns the name of the owning group, as a string, of the file indicated by {{path}} on the {{connection}}.
203
204===== file-last-modified-by
205
206<procedure>(file-last-modified-by connection path)</procedure>
207
208Returns the name of the user, as a string, who last changed the file indicated by {{path}} on the {{connection}}.
209
210==== File handles and low-level calls
211
212These calls are not on the protocol level, as the [[#9p-lolevel]] library procedures, but they are more low-level than the other procedures in the [[#9p-client]] library because they allow you to work on the file handle level.
213
214===== file-open
215
216<procedure>(file-open connection path mode)</procedure>
217
218Opens the file indicated by {{path}} on the {{connection}} with the given {{mode}} and returns an opaque handle object which you can use for the other procedures described in this section.  For bit flags that the {{mode}} can take, see [[#Open flags|the open flags section]].
219
220===== file-create
221
222<procedure>(file-create connection path permissions mode)</procedure>
223
224Creates and opens the file indicated by {{path}} on the {{connection}} with the given {{permission}} and {{mode}} and returns an opaque handle object which you can use for the other procedures described in this section.  For bit flags that the {{mode}} can take, see [[#Open flags|the open flags section]].  For bit flags that the {{permission}} can take, see [[#Permission bits|the permission bits section]].
225
226
227===== file-close
228
229<procedure>(file-close handle)</procedure>
230
231Close the file indicated by {{handle}}.  It is not an error to close a file more than once.
232
233===== file-read
234
235<procedure>(file-read handle size)</procedure>
236
237Read {{size}} bytes from the file with the given {{handle}}.  This procedure returns a list with two values: the buffer containing the data and the number of bytes read.
238
239===== file-write
240
241<procedure>(file-write handle buffer [size])</procedure>
242
243Writes the contents of the string or bytevector {{buffer}} into the file with the given {{handle}}. If the optional argument {{size}} is given, then only the specified number of bytes are written.
244
245===== set-file-position!
246
247<procedure>(set-file-position! handle position [whence])</procedure>
248
249Sets the current read/write position of {{handle}} to {{position}}, which should be an exact integer. {{whence}} specifies how the position is to interpreted and should be one of the values {{seek/set}}, {{seek/cur}} and {{seek/end}}. It defaults to {{seek/set}}.
250
251===== file-position
252
253<procedure>(file-position handle)</procedure>
254
255Returns the current read/write position of the {{handle}}.
256
257===== handle-stat
258
259<procedure>(handle-stat handle)</procedure>
260
261Just like [[#file-stat]], except it works on a handle instead of on a connection with a filename.
262
263===== Low-level handle access
264
265If you want to get really dirty and low-level you can modify file handles with the following procedures.  This is not recommended, but sometimes required if you want to do some custom things just above the protocol level and extend the client library instead of writing your own.
266
267====== path-walk
268
269<procedure>(path-walk connection path [starting-point])</procedure>
270
271Obtain a handle for the file identified by {{path}} on the {{connection}} ''without opening it''.  You must not forget to clunk the handle's FID (or just call {{file-close}} on the handle).  {{starting-point}} is an optional handle to a directory from which to start walking.  It defaults to the root directory (/).
272
273====== with-handle-to
274
275If all you need is a temporary handle/FID for a message to the server, you can use this utility procedure:
276
277<procedure>(with-handle-to connection path procedure)</procedure>
278
279This will call {{procedure}} with one argument: a temporary handle which represents the {{path}} on the {{connection}}.  After the procedure returns, the handle will be deallocated and the FID will no longer be valid.  This returns whatever {{procedure}} returned.  If a condition is signaled, the handle will be deallocated properly and the FID clunked.
280
281====== alloc-handle
282
283The 9p-client library keeps track of FIDs for you so you do not have to remember numbers.  If you wish to send low-level messages yourself you should allocate and release FIDs through the library so your FIDs can't clash with the FIDs the library uses:
284
285<procedure>(alloc-handle connection)</procedure>
286
287Allocate a handle on the connection.  This returns a handle object which you can query with the following procedures:
288
289<procedure>(handle-connection handle)</procedure>
290<procedure>(handle-fid handle)</procedure>
291<procedure>(handle-position handle)</procedure>
292<procedure>(handle-iounit handle)</procedure>
293
294The fid is allocated from an internal pool of free fids.  The position is initialized to 0, and used as an offset for read/write procedures (the server does not keep track of this for us in the 9p protocol).
295
296The iounit defaults to {{#f}} and you are expected to set it manually (normally, [[file-open]] and [[file-create]] do this for you). is returned as part of the Ropen and Rcreate replies and is the maximum size of a data transfer (either read or write).  If the server returns 0, the iounit should default to the size returned by {{connection-message-size}} minus 24.
297
298
299====== release-handle
300
301Once you are done with a handle, you must either pass the handle to [[file-close]] (or just disconnect with [[client-disconnect]]) or call {{release-handle}}:
302
303<procedure>(release-handle handle)</procedure>
304
305'''important''': be sure to clunk the handle's fid first.  {{release-handle}} does ''not'' clunk the fid.
306
307
308===== Sending messages
309
310A code using 9p-client normally never needs to send raw messages, but in case it does, there is one convenience procedure that does just a bit more than the raw [[#9p-lolevel]] procedures do:
311
312<procedure>(request connection type . args)</procedure>
313
314This creates a new {{message}} object (see below) with a tag and the given {{type}}.  {{args}} are the message-contents.  It then sends this request to the server and awaits a response.  The response should match the request (a {{Twhatever}} should result in a {{Rwhatever}} message), or a condition of type {{(exn 9p-response-error)}} is signaled.  If the server returns an error (via {{Rerror}}), a condition of type {{(exn 9p-server-error)}} is signaled.  The response object (a message object) is returned.
315
316=== 9p-lolevel
317
318This library allows you to build your own client or server abstraction library.  This documentation will not make a lot of sense if you haven't read the [[http://9p.cat-v.org/documentation|9p protocol documentation]].
319
320==== Usage
321
322  (use 9p-lolevel)
323  (use utf8)
324
325Again, utf8 is highly recommended but not strictly required.
326
327==== Messages
328
329Messages are main concept in the 9p protocol.  They can be created as follows:
330
331<procedure>(make-message type tag contents)</procedure>
332
333The type is a symbol, one of
334  Tversion Rversion
335  Tauth Rauth
336  Tattach Rattach
337  Rerror
338  Tflush Rflush
339  Twalk Rwalk
340  Topen Ropen
341  Tcreate Rcreate
342  Tread Rread
343  Twrite Rwrite
344  Tclunk Rclunk
345  Tremove Rremove
346  Tstat Rstat
347  Twstat Rwstat
348
349As you can see, all messages (except Rerror) come in pairs: there is a transmit message (that starts with a T) and a response message (that starts with an R).  The client sends transmit messages and the server sends response message in return.  It must either send the matching response message or Rerror.  It is not allowed to return a different message, nor is it allowed for the client to send a response message or the server to send a transmit message.
350
351The tag is a unique identifier that allows the client to keep track of what it sent and what responses belong to what transmissions.  The client sends a message with a given tag and the server will respond with the matching response message bearing the same tag.  This allows a client to send messages ''asynchronously'', as long as they all have a different tag.  Then the responses can come in any order and be handled at any time and still be understood if the client keeps a list of sent tags and what transmissions belonged to them.  The [[9p-client]] library always sends messages synchronously, waiting for replies before sending new transmissions.  This allows it to use a constant tag all the time.
352
353The contents are a list whose contents differ per message type.  For instance, a Tversion message's contents consist of an {{msize}} (a maximum message size) and a {{string}} which indicates the protocol version.  Currently the 9p-lolevel implicitly assumes the 9P2000 version of the protocol because of the way it is constructed.  If it turns out to be useful to support different versions, the egg's API will most likely change in order to allow for more flexibility.
354
355You can of course query and modify the message objects with the following procedures:
356<procedure>(message? object)</procedure>
357<procedure>(message-type message)</procedure>
358<procedure>(message-type-set! message new-type)</procedure>
359<procedure>(message-tag message)</procedure>
360<procedure>(message-tag-set! message new-tag)</procedure>
361<procedure>(message-contents message)</procedure>
362<procedure>(message-contents-set! message new-contents)</procedure>
363
364===== send-message
365
366<procedure>(send-message outport message)</procedure>
367
368Sends the {{message}} on the output-port {{outport}}.
369
370===== receive-message
371
372<procedure>(receive-message inport)</procedure>
373
374Waits for a message on input-port {{inport}} and returns a 9p message-object.
375 
376==== QIDs
377
378A QID is an unique identifier for a file on the server; two QIDs are the same iff they point to the same file.  A QID has three fields which can be queried with the following procedures:
379
380<procedure>(qid-type qid)</procedure>
381<procedure>(qid-version qid)</procedure>
382<procedure>(qid-path qid)</procedure>
383
384You can create a QID using the {{make-qid}} procedure:
385
386<procedure>(make-qid type version path)</procedure>
387
388Finally, you can check if an object is a QID object with the {{qid?}} predicate:
389
390<procedure>(qid? object)</procedure>
391
392The fields of the QID will be described next.
393
394First, the type of a QID is a bitwise field which consists of several of the following constants ORed together:
395
396<constant>qtfile</constant>
397 
398{{qtfile}} indicates that the file is, in fact, a file.  Because everything in Plan9 is a file, this is always true, even for directories.  It does ''not'' mean that the file is a regular file.
399 
400<constant>qtdir</constant>
401
402{{qtdir}} indicates that the file is a directory.
403
404<constant>qtappend</constant>
405
406{{qtappend}} indicates that the file is an append-only file.
407
408<constant>qtexcl</constant>
409
410{{qtexcl}} indicates that the file is marked for exclusive-use.  This means that only one client can have this file open at any time.
411
412<constant>qtauth</constant>
413
414{{qtauth}} indicates that the file is an authentication file established by AUTH messages.
415 
416<constant>qttmp</constant>
417
418{{qttmp}} indicates that the file is a "temporary file".  In practice this means that the file is not included in nightly backups.
419
420The version of a QID is a version number for the file which is incremented every time the file is modified.
421
422The path of a QID is an integer that is unique among all files in the file hierarchy (ie, this uniquely identifies the file in the FS).
423
424==== Permission bits
425
426The permissions below can be ORed together bitwise to produce the desired permission mode.  When creating new files, the execute bit is ignored by the server unless you're creating a directory, so it is safe to always include it.
427
428'''Note:''' The 9p protocol documentation is not very consistent in naming these.  Sometimes it refers to permissions as ''mode'', and sometimes as ''perm'' or ''permission''.  On other occasions, it refers to the [[#Open flags|open flags]] as ''mode''.  Read carefully and check the context!
429
430<constant>perm/irusr</constant>
431<constant>perm/iwusr</constant>
432<constant>perm/ixusr</constant>
433
434These constants determine the permissions for the user who owns the file: read, write and execute, respectively.
435
436<constant>perm/irgrp</constant>
437<constant>perm/iwgrp</constant>
438<constant>perm/ixgrp</constant>
439
440These constants determine the permissions for the group that owns the file: read, write and execute, respectively.
441
442<constant>perm/iroth</constant>
443<constant>perm/iwoth</constant>
444<constant>perm/ixoth</constant>
445
446These constants determine the permissions for others: read, write and execute, respectively.
447
448There are some additional "permissions" that can be used on {{Tcreate}} messages, which are not really permissions but rather modes that change the way the file behaves (hence the inconsistence of the docs).  These are like the 'special' bits in Unix like sticky/setuid etc.  These are the following:
449
450<constant>dmdir</constant>
451
452This is used to create directories instead of files with {{Tcreate}}.
453
454<constant>dmappend</constant>
455
456The file can only be appended to.
457
458<constant>dmexcl</constant>
459
460The file is 'exclusive', it can only be opened by one client at a time.
461
462<constant>dmauth</constant>
463
464The file is an authentication file, as established by AUTH messages.
465
466<constant>dmtmp</constant>
467
468The file is to be considered "temporary".  In practice this means that it is not included in nightly backups.
469
470==== Open flags
471
472These flags are useful when opening a new file (for use in the {{Topen}}/{{Tcreate}} messages).  These can be ORed together bitwise to produce the desired mode.
473
474<constant>open/rdonly</constant>
475
476The file is to be opened only for reading.
477
478<constant>open/wronly</constant>
479
480The file is to be opened only for writing.
481
482<constant>open/rdwr</constant>
483
484The file is to be opened both for reading and writing.
485
486<constant>open/trunc</constant>
487
488The file is to be truncated on opening.
489
490<constant>open/rclose</constant>
491
492The file is to be removed upon closing (ie, when the FID is clunked).
493
494==== Utility procedures
495
496===== data->directory-listing
497
498<procedure>(data->directory-listing data show-dotfiles?)</procedure>
499
500Because the 9p protocol requires you to use the {{Tread}}/{{Rread}} messages to read both from files and directories, the {{Rread}} response can be considered to be a polymorphic type.  In case of files, the data is simply a bytestream, but in case of directories, the data will be structured.  This means the data needs to be decoded.
501
502This procedures decodes the {{data}} obtained from the {{Rread}} message and returns a list of filenames which are the directory listing for the directory that was read.  If {{show-dotfiles?}} is {{#f}} files starting with a dot are excluded from the list.
503
504Note: The converse procedure, {{directory-listing->data}}, is currently not implemented.
505
506=== Example
507
508==== 9p-client
509
510Here's a simple example that talks to a wmii server.  Note that if
511you're really looking for a Scheme library to script your wmiirc
512files, you probably want the [[wmiirc]] egg instead of using the raw
5139p protocol directly.
514
515<examples><example>
516<init>
517(require-library 9p-client unix-sockets)
518
519(import (prefix 9p-client 9p:))
520</init>
521<expr>
522(receive (in out)
523    (unix-connect (sprintf "/tmp/ns.~A.:0/wmii" (getenv "USER")))
524  (let ((con (9p:client-connect in out)))
525    (printf "Current tabs on left bar: ~A\n" (9p:directory con "/lbar"))
526    (printf "Label on first tab on left bar: ~A\n"
527            (9p:with-input-from-file con `("lbar" ,(car (9p:directory con "/lbar"))) read-string ))
528    ;; Write something to the right bar
529    (9p:with-output-to-file con "/rbar/status" (lambda () (printf "Yo, what's up?")))
530    (9p:client-disconnect con)))
531</expr>
532</example></examples>
533
534This prints something like
535
536  Current tabs on left bar: (3 2 1)
537  Label on first tab on left bar: #888888 #222222 #333333 3
538
539And it shows the string "Yo, what's up?" on your status bar.
540
541=== Changelog
542
543* 0.8 Change to install only one extension "9p". This simplifies uninstallation.
544* 0.7 Fix import handling for unit "files"
545* 0.6 Port to hygienic Chicken, fix handle auto-closing bug
546* 0.5 Add mutex handling to ensure thread-safety
547* 0.4 When reading files, EOF gets reported only when the file really is at an end (fixes reading from FIFO-like files)
548* 0.3 Don't allow output 9p ports to return values when all other values are (void), allow multiple return values in with-output/with-input thunks
549* 0.2 Fix 9p:open-output-file bug, make path-walk accept handles
550* 0.1 Initial release
551
552
553=== License
554
555  Copyright (c) 2008, Peter Bex
556  All rights reserved.
557 
558  Redistribution and use in source and binary forms, with or without
559  modification, are permitted provided that the following conditions are
560  met:
561 
562  Redistributions of source code must retain the above copyright
563  notice, this list of conditions and the following disclaimer.
564 
565  Redistributions in binary form must reproduce the above copyright
566  notice, this list of conditions and the following disclaimer in the
567  documentation and/or other materials provided with the distribution.
568 
569  Neither the name of the author nor the names of its contributors may
570  be used to endorse or promote products derived from this software
571  without specific prior written permission.
572 
573  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
574  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
575  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
576  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
577  COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
578  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
579  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
580  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
581  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
582  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
583  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
584  OF THE POSSIBILITY OF SUCH DAMAGE.
Note: See TracBrowser for help on using the repository browser.