#lang scribble/doc @(require scribble/manual (for-label scheme (planet "enumeration.ss" ("untyped" "unlib.plt" 3 20)) "../errno.ss" "../server.ss")) @title[#:tag "server"]{Server} @defmodule[(planet "server.ss" ("murphy" "9p.plt" 1 0))]{ This module re-exports all bindings from the @scheme["server/filesystem.ss"], @scheme["server/handle.ss"] and @scheme["server/util.ss"] modules. } @section[#:tag "server/data"]{Supporting Data Structures} @defmodule[(planet "data.ss" ("murphy" "9p.plt" 1 0) "server")]{ Utilities to support the operation of the 9P server. } @defform[(touch-mode id) #:contracts ([id (symbols 'name 'length 'mode 'mtime 'gid)])]{ Enumeration of possible requested operations when changing the directory entry for a file. The transformer is defined using @scheme[define-enum]. } @defclass[server-context% object% ()]{ Server side representation of user rights in a filesystem. Encapsulates all access control operations. @defconstructor[([user string?])]{ Initializes the new context with the given user name. } @defmethod[#:mode public-final (->user) string?]{ Unwraps the user name contained in the context. } @defmethod[#:mode public (in-group? [group string?]) any/c]{ Checks whether the user of this context is a member of the given group. The default implementation returns @scheme[#t] iff the user name is the same as the group name. } @defmethod[#:mode public (can-access? [file (is-a?/c server-file%)] [mode natural-number/c]) any/c]{ Checks whether the user of this context is allowed to access the given @scheme[file] using the open mode @scheme[mode]. The default implementation checks the @scheme[open-mode-direction] of the mode against the permission bits of the file as obtained by calling @method[server-file<%> read-stat] on the file. If the user of this context is the owner of the file, the @scheme[file-mode-user] is checked, if she is a member of the file's owning group, the @scheme[file-mode-group] is checked, otherwise the @scheme[file-mode-others] is checked. } @defmethod[#:mode public (can-touch? [file (is-a?/c server-file%)] [mode (or/c symbol? (listof symbol?))]) any/c]{ Checks whether the user of this context is allowed to modify the directory entry of the given @scheme[file] changing one of the properties indicated by @scheme[mode]. The default implementation allows @itemlist[ @item{ @schemeid[name] changes to anybody with write access to the file's @method[server-file<%> parent], } @item{ @schemeid[length] truncation to anybody with write access to the file itself, } @item{ @schemeid[mode] changes to the file's owner if she also has write access to the file's @method[server-file<%> parent] and } @item{ @schemeid[mtime] changes to the file's owner if she also has write access to the file itself. } ] Changes to the file's @schemeid[gid] are forbidden by default. } @defmethod[#:mode public (can-remove? [file (is-a?/c server-file%)]) any/c]{ Checks whether the user of this context is allowed to delete the given @scheme[file]. The default implementation allows anyone with write access to both the file itself and to its @method[server-file<%> parent] to delete it. } } @section[#:tag "server/filesystem"]{Filesystem} @defmodule[(planet "filesystem.ss" ("murphy" "9p.plt" 1 0) "server")]{ Implementation of the @scheme[filesystem<%>] interface for the server side. } @defclass[server-filesystem% object% (filesystem<%>)]{ Server side implementation of a 9P filesystem. @defconstructor[([with-root (is-a?/c server-directory%)] [with-roots dict?] [port-no (integer-in 0 65535) 564] [hostname (or/c string? #f) #f] [max-allow-wait natural-number/c 4] [reuse? any/c #f] [9wrapper (or/c path-string? #f) (find-executable-path "9")])]{ Listens for TCP connections at @scheme[hostname] and @scheme[port-no] and starts a server event loop. Either @scheme[with-root] or @scheme[with-roots] must be specified to determine which directories are served as filesystem roots: @itemlist[ @item{ If @scheme[with-root] is used, the server offers a single root directory under the default name @scheme[""]. } @item{ If @scheme[with-roots] is used, it should be passed a dictionary mapping root names to directory objects. } ] If @scheme[9wrapper] is not @scheme[#f] it should be a path to the @scheme["9"] command line program from the @link["http://swtch.com/plan9port/"]{"Plan9 from User Space"} utility collection. If this program is available, the convenience methods @method[server-filesystem% mount] and @method[server-filesystem% unmount] can be used. } @defmethod[#:mode public (make-context [user string?]) (is-a?/c server-context%)]{ Creates a new access control context for the given @scheme[user]. } @defmethod[#:mode override (authenticate [root string?] [#:user user string?]) (is-a?/c server-file-handle%)]{ Opens an authentication channel suitable as a token when attaching to the filesystem root directory @scheme[root] as the given @scheme[user]. The default implementation just raises a filesystem exception with the message @scheme[ENOSYS], indicating that authentication is not required. } @defmethod[#:mode override-final (attach [root string?] [#:user user string?] [#:token auth (or/c (is-a?/c server-file-handle%) #f)]) (is-a?/c server-directory-handle%)]{ Opens the filesystem root directory @scheme[root] as the given @scheme[user]. Finds the root directory with the given name, invokes @method[server-filesystem% make-context] to create a new access control context for the given @scheme[user] and finally invokes attach] on the root directory to create the actual file handle to return. } @defmethod[#:mode public-final (mount [mountpoint path-string?]) any/c]{ Mounts the default root directory with the name @scheme[""] offered by this server in the local filesystem at the path @scheme[mountpoint]. Returns whether the external command called to perform the mount was successful. } @defmethod[#:mode public-final (unmount) any/c]{ Unmounts the default root directory in the local filesystem. Returns whether the root was previously mounted and is now successfully unmounted. } @defmethod[#:mode override (clunk) void?]{ Refine this method with @scheme[augment]. Unmounts the filesystem's default root if it is mounted and drops all connections to the whole filesystem. } } @section[#:tag "server/handle"]{Files and Handles} @defmodule[(planet "handle.ss" ("murphy" "9p.plt" 1 0) "server")]{ Implementations of the @scheme[file-handle<%>] and @scheme[directory-handle<%>] interfaces for the server side. } @defclass[server-file-handle% object% (file-handle<%>)]{ Server side container for an access context and an actual object representing a file. @defconstructor[([file (is-a?/c server-file%)] [context (is-a?/c server-context%)] [current-i/o-state any/c #f])]{} @defmethod[#:mode public-final (->file) (is-a?/c server-file%)]{ Unwraps the file accessed by this handle or raises an exception if the handle has already been invalidated. } @defmethod[#:mode public-final (->context) (is-a?/c server-context%)]{ Unwraps the access control context contained in this handle. } @defmethod[#:mode override-final (walk [name string?] ...) (is-a?/c server-file-handle%)]{ Moves from the file represented by this handle to a different file and returns a file handle for the target. Delegates its work to @method[server-file-handle% walk-self] for an empty walk, @method[server-file-handle% walk-parent] for a walk to @scheme[".."] or @method[server-file-handle% walk-child] for a walk to the name of some child. Walks of multiple steps are automatically broken down into one step per method call. } @defmethod[#:mode public-final (walk-self) (is-a?/c server-file-handle%)]{ Creates another handle for the file represented by this handle. Uses the @method[server-file% attach] method of the underlying file to obtain a new handle. } @defmethod[#:mode public-final (walk-parent) (is-a?/c server-directory-handle%)]{ Moves from the file represented by this handle to its parent directory and returns a handle for that directory. Uses the @method[server-file<%> parent] method of the underlying file to find the parent directory and invokes @method[server-file% attach] on it to obtain a new handle. } @defmethod[#:mode public (walk-child [name string?]) (is-a?/c server-file-handle%)]{ Moves from the file represented by this handle to a child and returns a handle for that child. The default implementation just raises a filesystem exception with the message @scheme[ENOTDIR]. } @defmethod[#:mode override-final (read-stat) stat?]{ Obtains the directory entry information for the file. Invokes @method[server-file<%> read-stat] on the underlying file. } @defmethod[#:mode override-final (write-stat [stat stat?]) void?]{ Changes the directory entry information for the file. Verifies the validity of the @scheme[stat] argument and checks permissions by invoking @method[server-context% can-touch?] on the underlying access control context. If everything is in order, @method[server-file<%> write-stat] is invoked on the underlying file. The method may raise a filesystem exception with the message @scheme[EINVAL] or @scheme[EACCESS] if the argument validity checks or the access permission checks fail. } @defmethod[#:mode public-final (i/o-state) any/c]{ Retrieves the current I/O state of the handle. The file is open as viewed from this handle iff the return value is not @scheme[#f]. } @defmethod[#:mode override-final (open [mode natural-number/c]) natural-number/c]{ Opens the file for reading or writing data and returns the maximum I/O unit size. Checks access permissions by invoking @method[server-context% can-access?] on the underlying access control context. If everything is in order, @method[server-file<%> open] is invoked on the underlying file and the returned I/O state is stored in the handle. The method may raise a filesystem exception with the message @scheme[EACCESS] if the access permission check fails. } @defmethod[#:mode override-final (read [size natural-number/c] [offset natural-number/c]) (or/c bytes? eof-object?)]{ Reads data from the file after it has been opened for reading. Invokes @method[server-file<%> read] on the underlying file if an I/O state is associated with the handle. The method may raise a filesystem exception with the message @scheme[ENOTCONN] if the handle has no associated I/O state. } @defmethod[#:mode override-final (write [data bytes?] [offset natural-number/c]) natural-number/c]{ Writes data to the file after it has been opened for writing. Invokes @method[server-file<%> write] on the underlying file if an I/O state is associated with the handle. The method may raise a filesystem exception with the message @scheme[ENOTCONN] if the handle has no associated I/O state. } @defmethod[#:mode override (clunk) void?]{ Refine this method with @scheme[augment]. A refinement will also be called if the file is removed using @method[server-file-handle% remove]. Closes the file if it is open and invalidates the file handle. Invokes @method[server-file<%> clunk] on the underlying file if an I/O state is associated with the handle. } @defmethod[#:mode override-final (remove) void?]{ Closes the file if it is open, removes it from the storage device and invalidates the file handle. Invokes @method[server-file<%> remove] on the underlying file. } } @defclass[server-directory-handle% server-file-handle% (directory-handle<%>)]{ Server side container for an access context and an actual object representing a directory. @defmethod[#:mode override-final (walk-child [name string?]) (is-a?/c server-file-handle%)]{ Moves from the file represented by this handle to a child and returns a handle for that child. Checks execute permissions by invoking @method[server-context% can-access?] on the underlying access control context. If everything is in order, @method[server-directory<%> child] is invoked on the underlying directory to obtain the child and @method[server-file% attach] is used on the child to produce a file handle to be returned. The method may raise a filesystem exception with the message @scheme[EACCESS] if the access permission check fails. } @defmethod[#:mode override-final (in-entries) sequence?]{ Returns an iterable sequence of directory entries. Checks read permissions by invoking @method[server-context% can-access?] on the underlying access control context. If everything is in order, @method[server-directory<%> in-entries] is invoked on the underlying directory. The method may raise a filesystem exception with the message @scheme[EACCESS] if the access permission check fails. } @defmethod[#:mode override-final (create [name string?] [perm natural-number/c] [mode natural-number/c]) (values (is-a?/c server-file-handle%) natural-number/c)]{ Creates a new entry in the directory. Checks write permissions by invoking @method[server-context% can-access?] on the underlying access control context. If everything is in order, @method[server-directory<%> create] is invoked on the underlying directory to obtain a new child and @method[server-file% attach] is used on the child to produce a file handle and I/O unit size to be returned. The method may raise a filesystem exception with the message @scheme[EACCESS] if the access permission check fails. } } @definterface[server-file<%> ()]{ The implicit interface of the class @scheme[server-file%]. @defmethod[#:mode public (parent) (is-a?/c server-directory%)]{ Retrieves the parent directory of this file. The default implementation just raises a filesystem with the message @scheme[ENOSYS], but this behaviour is rarely useful so you should normally override this method or use a mixin like @scheme[server-file:parent-mixin] to implement it. } @defmethod[#:mode public (read-stat [context (is-a?/c server-context%)]) stat?]{ Obtains the directory entry information for the file. The default implementation just raises a filesystem exception with the message @scheme[ENOSYS]. } @defmethod[#:mode public (write-stat [context (is-a?/c server-context%)] [stat stat?]) void?]{ Changes the directory entry information for a file. The default implementation just raises a filesystem exception with the message @scheme[EROFS]. } @defmethod[#:mode public (open [context (is-a?/c server-context%)] [mode natural-number/c]) (values any/c natural-number/c)]{ Opens the file for I/O operations and returns some object serving as the I/O state and the maximum I/O unit size. The default implementation just returns @scheme[(values #t (- (max-message-size) 24))]. } @defmethod[#:mode public (read [context (is-a?/c server-context%)] [i/o-state any/c] [size natural-number/c] [offset natural-number/c]) (or/c bytes? eof-object?)]{ Reads data from the file after it has been opened for reading. The default implementation constantly returns @scheme[eof]. } @defmethod[#:mode public (write [context (is-a?/c server-context%)] [i/o-state any/c] [data bytes?] [offset natural-number/c]) natural-number/c]{ Writes data to the file after it has been opened for writing. The default implementation just raises a filesystem exception with the message @scheme[EROFS]. } @defmethod[#:mode public (clunk [context (is-a?/c server-context%)] [i/o-state any/c]) void?]{ Disposes of an I/O context when the file is closed. The default implementation does nothing. } @defmethod[#:mode public (remove [context (is-a?/c server-context%)]) void?]{ Deletes the file from the storage medium. The default implementation just raises a filesystem exception with the message @scheme[EROFS]. } } @defclass[server-file% object% (server-file<%>)]{ Server side file access object. @defmethod[#:mode public (attach [context (is-a?/c server-context%)] [mode (or/c natural-number/c #f) #f]) (if mode (values (is-a?/c server-file-handle%) natural-number/c) server-file-handle%)]{ Creates a new file handle connected to this file. The behaviour of the method depends on the value of the @scheme[mode] argument: @itemlist[ @item{ If the @scheme[mode] is a natural number, the file is immediately opened for I/O operations with this mode and an I/O state is associated with the new file handle. The method returns the new file handle and the I/O unit size in this case. } @item{ If the @scheme[mode] is @scheme[#f], the file is not opened for I/O operations. The new file handle has no initial I/O state. The method only returns the new file handle in this case. } ] The default implementation creates a new instance of @scheme[server-file-handle%] referencing the file and containing the given @scheme[context]. If applicable, an I/O state for the new handle is created by invoking @method[server-file-handle% open]. } } @defclass[server-file-cursor% object% ()]{ Helper class to implement read and write methods for files. @defconstructor[([current-offset natural-number/c 0] [with-i/o-unit natural-number/c (- (max-message-size) 24)])]{ Initializes the cursor object with a starting @scheme[offset] in the file and an maximum I/O unit size. } @defmethod*[#:mode public-final ([(offset) natural-number/c] [(offset [new-offset natural-number/c]) void?])]{ Retrieves or changes the current file offset of the cursor. If the cursor has been invalidated the method raises a filesystem exception with the message @scheme[ENOTCONN]. } @defmethod[#:mode public-final (i/o-unit) natural-number/c]{ Retrieves the maximum I/O unit size of the cursor. } @defmethod[#:mode pubment (read [size natural-number/c] [at-offset natural-number/c (offset)]) (or/c bytes? eof-object?)]{ Reads data from the cursor at the specified position. The method ensures that refinements are never passed requested sizes larger than the maximum I/O unit and that the current position of the cursor is correctly tracked based on the incoming @scheme[at-offset] value and the outgoing result. The default implementation constantly returns @scheme[eof]. } @defmethod[#:mode pubment (write [data bytes?] [at-offset natural-number/c (offset)]) natural-number/c]{ Writes data to the cursor at the specified position. The method ensures that the current position of the cursor is correctly tracked based on the incoming @scheme[at-offset] value and the outgoing result of the refinements. The default implementation just raises a filesystem exception with the message @scheme[EROFS]. } @defmethod[#:mode pubment (clunk) void?]{ Invalidates the cursor. } } @defmixin[server-file:cursor-mixin (server-file<%>) ()]{ Mixin that delegates all read and write operations from a file class to cursor objects. @defmethod[#:mode public (make-cursor [context (is-a?/c server-context%)] [mode natural-number/c]) (is-a?/c server-file-cursor%)]{ Creates a new cursor for the file using the given open @scheme[mode]. The default implementation just raises a filesystem exception with the message @scheme[ENOSYS]. } @defmethod[#:mode override-final (open [context (is-a?/c server-context%)] [mode natural-number/c]) (values (is-a?/c server-file-cursor%) natural-number/c)]{ Opens the file for I/O operations and returns a cursor obtained by a call to @method[server-file:cursor-mixin make-cursor] together with its maximum I/O unit size. } @defmethod[#:mode override-final (read [context (is-a?/c server-context%)] [i/o-state (is-a?/c server-file-cursor%)] [size natural-number/c] [offset natural-number/c]) (or/c bytes? eof-object?)]{ Delegates the operation to @method[server-file-cursor% read]. } @defmethod[#:mode override-final (write [context (is-a?/c server-context%)] [i/o-state (is-a?/c server-file-cursor%)] [data bytes?] [offset natural-number/c]) natural-number/c]{ Delegates the operation to @method[server-file-cursor% write]. } @defmethod[#:mode override-final (clunk [context (is-a?/c server-context%)] [i/o-state (is-a?/c server-file-cursor%)]) void?]{ Delegates the operation to @method[server-file-cursor% clunk]. } } @defclass[server-directory-cursor% server-file-cursor% ()]{ Generic cursor for directories. @defconstructor/auto-super[([entries sequence?])]{ Initializes the cursor object with the given sequence of @scheme[stat] directory @scheme[entries]. } @defmethod[#:mode augment-final (read [size natural-number/c] [at-offset natural-number/c]) (or/c bytes? eof-object?)]{ Reads packed binary representations of the directory entries from the underlying sequence. Only allows the offset to be the one where the last read operation left off or zero to reset the sequence iteration to the beginning. } @defmethod[#:mode augment-final (write [data bytes?] [at-offset natural-number/c]) natural-number/c]{ Always fails with a filesystem exception with the message @scheme[EISDIR]. } @defmethod[#:mode pubment (clunk) void?]{ Resets the sequence iteration and invalidates the cursor. } } @definterface[server-directory<%> (server-file<%>)]{ The implicit interface of the class @scheme[server-directory%]. @defmethod[#:mode public (child [name string?]) (is-a?/c server-file%)]{ Finds the file with a given @scheme[name] in the directory. The default implementation just raises a filesystem exception with the message @scheme[ENOENT]. } @defmethod[#:mode public (in-entries [context (is-a?/c server-context%)]) sequence?]{ Generates a sequence of @scheme[stat] directory entries for this directory. The special entries with the names @scheme["."] and @scheme[".."] that are usually visible to the user @emph{should not} be generated. The default implementation returns an empty sequence. } @defmethod[#:mode public (create [context (is-a?/c server-context%)] [name string?] [perm natural-number/c] [mode natural-number/c]) (is-a?/c server-file%)]{ Creates a new entry in the directory. The open @scheme[mode] is passed for completeness' sake in case the flags make a difference in the setup of the new file, but the returned file also gets an explicit synthetic @method[server-file<%> open] call if this method is invoked through @method[server-directory-handle% create]. } } @defclass[server-directory% (server-file:cursor-mixin server-file%) (server-directory<%>)]{ Server side directory access object. @defmethod[#:mode override-final (make-cursor [context (is-a?/c server-context%)] [mode natural-number/c]) (is-a?/c server-directory-cursor%)]{ Creates a @scheme[server-directory-cursor%] based on a sequence returned by @method[server-directory<%> in-entries]. } @defmethod[#:mode override (attach [context (is-a?/c server-context%)] [mode (or/c natural-number/c #f) #f]) (if mode (values (is-a?/c server-directory-handle%) 0) (is-a?/c server-directory-handle%))]{ Creates a new directory handle connected to this directory. Since directories are never implicitly opened when they are created, the @scheme[mode] argument is effectively ignored. If it is given, the seconds return value of the method is constantly @scheme[0] in the default implementation. The default implementation creates a new instance of @scheme[server-directory-handle%] referencing the file and containing the given @scheme[context]. } } @section[#:tag "server/util"]{Additional Utilities} @defmodule[(planet "util.ss" ("murphy" "9p.plt" 1 0) "server")]{ Convenience classes to implement file systems in memory and to convert byte strings and ports into file cursors. } @definterface[server-file/stat<%> (server-file<%>)]{ Extension of the basic file interface with directory entry information. Although not canonical for on-disk filesystems, associating the directory entry information directly with the file objects is useful for virtual in-memory filesystems. @defmethod[#:mode public-final (on-name-change [key any/c] [listener (or/c (-> string? (or/c string? #f) any) #f)]) void?]{ Register or unregister a @scheme[listener] to be invoked when the directory entry data for the file is changed and its name changes or if the file is removed. The listeners get the old and new names of the file as arguments. If the file is removed, the new name is set to @scheme[#f]. The @scheme[key] is some arbitrary value identifying the listener. To remove the callback again, provide the same key and set the listener argument to @scheme[#f]. } @defmethod*[#:mode public-final ([(name) string?] [(name [new-name string?]) void?])]{ Retrieve or change the current name of the file. If the name is changed, registered change listeners are called with the old and new names. } @defmethod[#:mode public (content-length) natural-number/c]{ Retrieve the length of the file's content as it should be reported in the directory entry for the file. The default implementation constantly returns @scheme[0]. } @defmethod[#:mode public-final (touch [context (is-a?/c server-context%)] [modified? any/c] [time natural-number/c (current-seconds)]) void?]{ Updates the access time of the file to @scheme[time]. If @scheme[modified?] isn't @scheme[#f], the modification time of the file is also updated. The new value of the @scheme[stat-muid] field is taken from the username stored in the @scheme[context]. } @defmethod[#:mode pubment (truncate [context (is-a?/c server-context%)] [time natural-number/c (current-seconds)]) void?]{ Empties the file and updates its modification time. The default implementation just raises a filesystem exception with the message @scheme[EINVAL]. } } @defmixin[server-file:stat-mixin (server-file<%>) (server-file/stat<%>)]{ Simple implementation of directory entry information in memory. @defconstructor/auto-super[([current-name string?] [mode natural-number/c] [uid string? (or (getenv "USER") "nobody")] [gid string? uid] [muid string? uid] [mtime natural-number/c (current-seconds)] [atime natural-number/c mtime] [type natural-number/c 0] [dev natural-number/c 0] [version natural-number/c 0] [path natural-number/c (eq-hash-code this)])]{ Initializes the file object with values for the fields of the directory entry record. } @defmethod[#:mode override-final (read-stat [context (is-a?/c server-context%)]) stat?]{ Synthesizes a @scheme[stat] directory entry from the file object's fields. The @scheme[stat-mode] gets a directory bit added automatically if the file is a directory. The @scheme[stat-qid] is created from the file type bits of the @scheme[stat-mode] and the version and path values passed to the constructor. The @scheme[stat-length] is taken from a call to the @method[server-file/stat<%> content-length] method. } @defmethod[#:mode override-final (write-stat [context (is-a?/c server-context%)] [stat stat?]) void?]{ This method takes the following actions to update the directory entry information for the file: @itemlist[ @item{ If a new name is given in the @scheme[stat] argument, @method[server-file/stat<%> name] is used to change the name of the file and notify any interested listeners. } @item{ If a new group identifier is given in the @scheme[stat] argument, the new value is stored. } @item{ If a new file mode is given in the @scheme[stat] argument, the new value is stored. } @item{ If a new length of @scheme[0] is given in the @scheme[stat] argument, the file is emptied with a call to @method[server-file/stat<%> truncate]. } @item{ The file's access time is updated. If the file was truncated or a modification time was explicitly specified in the @scheme[stat] argument, the modification time is also updated and the user identifier responsible for the last modification is taken from the user name contained in the @scheme[context] argument. } ] } @defmethod[#:mode override (remove [context (is-a?/c server-context%)]) void?]{ Refine this method with @scheme[augment]. Unless the refinement method raises an exception, the file's name is set to @scheme[#f] and all name change listeners are notified to indicate that the file is no longer available. } } @definterface[server-file/parent<%> (server-file<%>)]{ Extension of the basic file interface with parent tracking. @defmethod*[#:mode override ([(parent) (is-a?/c server-directory%)] [(parent [new-parent (or/c (is-a?/c server-directory%) #f)]) void?])]{ Retrieves or sets the parent directory of the file. If the parent is set to @scheme[#f], reading it causes a filesystem exception with the message @scheme[ENOSYS]. } } @defmixin[server-file:parent-mixin (server-file<%>) (server-file/parent<%>)]{ Simple implementation of parent tracking in memory. @defconstructor/auto-super[([current-parent (or/c (is-a?/c server-directory%) #f) (and (is-a? this server-directory%) this)])]{ Initializes the file with its initial parent. By default directories are used as their own parents, which is suitable for filesystem roots. Files don't have a default parent, but adding files to @scheme[server-hash-directory%] instances also sets the parent information, so an explicit constructor argument to set the parent is often unnecessary. } } @defclass[server-bytes-cursor% server-file-cursor% ()]{ Generic cursor accessing byte strings. @defconstructor/auto-super[([current-content bytes?] [can-read? any/c #t] [can-write? any/c (not (immutable? current-content))] [can-resize? any/c can-write?] [commit (or/c (-> bytes? any) #f) #f])]{ Initializes the cursor with content to read or write and a specification of its operation mode. } @defmethod*[#:mode public-final ([(content) bytes?] [(content [new-content bytes?]) void?])]{ Retrieves or sets the content currently visible through the cursor. } @defmethod[#:mode augment-final (read [size natural-number/c] [at-offset natural-number/c]) (or/c bytes? eof-object?)]{ Reads data from the cursor. If the @schemeid[can-read?] constructor parameter was @scheme[#f], the method always raises a filesystem exception with the message @scheme[EPERM]. } @defmethod[#:mode augment-final (write [data bytes?] [at-offset natural-number/c]) natural-number/c]{ Writes data to the cursor. If the new @scheme[data] doesn't fit into the existing byte string representing the content at @scheme[at-offset], a new content buffer is allocated unless the @schemeid[can-resize?] constructor argument was @scheme[#f] in which case the data to be written is truncated. If a write offset completely outside the current content buffer is given, the method raises a filesystem exception with the message @scheme[EFBIG]. If the @schemeid[can-write?] constructor parameter was @scheme[#f], the method always raises a filesystem exception with the message @scheme[EPERM]. } @defmethod[#:mode pubment (clunk) void?]{ Unless the refinement raises an exception, if a @schemeid[commit] procedure was specified as a constructor argument it is applied to the final byte string content of the cursor. } } @defclass[server-bytes-file% (server-file:cursor-mixin (server-file:parent-mixin (server-file:stat-mixin server-file%))) ()]{ In-memory file with byte string storage. @defconstructor/auto-super[([current-content bytes? #""])]{ Initializes the file with its content as a byte string. } @defmethod*[#:mode public-final ([(content) bytes?] [(content [new-content bytes?]) void?])]{ Retrieves or sets the content of the file. } @defmethod[#:mode public-final #;override-final (content-length [context (is-a?/c server-context%)]) natural-number/c]{ Overrides @method[server-file/stat<%> content-length]. This method is final, so it cannot be overridden. Determines the length of the file's current content in bytes. } @defmethod[#:mode augment-final (truncate [context (is-a?/c server-context%)] [time natural-number/c]) void?]{ Resets the file's current content to the empty byte string. } @defmethod[#:mode override-final (make-cursor [context (is-a?/c server-context%)] [mode natural-number/c]) (is-a?/c server-bytes-cursor%)]{ Creates an instance of @scheme[server-bytes-cursor%] suitable for the given open @scheme[mode] that accesses the current content of the file. If the cursor is writable, changes are committed to the file once the cursor is clunked. } } @defclass[server-value-file% (server-file:cursor-mixin (server-file:parent-mixin (server-file:stat-mixin server-file%))) ()]{ In-memory file with arbitrary Scheme value storage. @defconstructor/auto-super[([current-content any/c (void)])]{ Initializes the file with its contents, which can be any value. } @defmethod*[#:mode public-final ([(content) any/c] [(content [new-content any/c]) void?])]{ Retrieves or sets the content of the file. } @defmethod[#:mode public-final (content->bytes) bytes?]{ Converts the content of the file into a byte string using @scheme[write] to a byte string port. The result of this method is cached as long as the content value is not changed. } @defmethod[#:mode public-final #;override-final (content-length [context (is-a?/c server-context%)]) natural-number/c]{ Overrides @method[server-file/stat<%> content-length]. This method is final, so it cannot be overridden. Determines the byte length of the file's content as a byte string. } @defmethod[#:mode augment-final (truncate [context (is-a?/c server-context%)] [time natural-number/c]) void?]{ Resets the file's content to @scheme[(void)]. } @defmethod[#:mode override-final (make-cursor [context (is-a?/c server-context%)] [mode natural-number/c]) (is-a?/c server-bytes-cursor%)]{ Creates an instance of @scheme[server-bytes-cursor%] suitable for the given open @scheme[mode] that accesses the current content of the file as a byte string. If the cursor is writable, the changed bytes are @scheme[read] back in and stored as the new content of the file once the cursor is clunked. If the modified contents of the file cannot be read as an S-expression, the clunk operation raises a filesystem exception with the message @scheme[EIO]. } } @defclass[server-port-cursor% server-file-cursor% ()]{ Generic cursor accessing input and output ports. @defconstructor/auto-super[([input-port (or/c input-port? #f) #f] [block? any/c #f] [output-port (or/c output-port? #f) #f] [flush? any/c #f] [close? any/c #t] [cleanup (or/c (-> any) #f) #f])]{ Initializes the cursor with the given input and output ports and a specification of its operation mode. } @defmethod[#:mode public-final (->input-port) (or/c input-port? #f)]{ Unwraps the underlying input port of the cursor. } @defmethod[#:mode public-final (->output-port) (or/c output-port? #f)]{ Unwraps the underlying output port of the cursor. } @defmethod[#:mode augment-final (read [size natural-number/c] [at-offset natural-number/c]) (or/c bytes? eof-object?)]{ Reads data from the underlying input port. @scheme[at-offset] must either be @scheme[0] or the offset where the last read or write operation on the cursor left off. If the constructor parameter @schemeid[block?] was not @scheme[#f], @scheme[read-bytes!] is used to read data, otherwise @scheme[read-bytes-avail!] is used. If the cursor has no underlying input port, the method raises a filesystem exception with the message @scheme[EPERM]. } @defmethod[#:mode augment-final (write [data bytes?] [at-offset natural-number/c]) natural-number/c]{ Writes data to the underlying output port. @scheme[at-offset] must either be @scheme[0] or the offset where the last read or write operation on the cursor left off. If the constructor parameter @schemeid[flush?] was not @scheme[#f], @scheme[flush-output] is applied to the output port after the @scheme[data] has been written to it. If the cursor has no underlying input port, the method raises a filesystem exception with the message @scheme[EPERM]. } @defmethod[#:mode pubment (clunk) void?]{ Invokes the refinement method, closes the underlying ports unless the constructor parameter @schemeid[close?] was @scheme[#f] and calls the specified cleanup thunk if the constructor parameter @schemeid[cleanup] was not @scheme[#f]. All these operations are performed, even if some of them raise exceptions. The last raised exception is re-raised before the method returns. } } @defclass[server-log-file% (server-file:parent-mixin (server-file:stat-mixin server-file%)) ()]{ In-memory file providing access to Scheme log messages. @defconstructor/auto-super[([logger logger? (current-logger)] [current-log-level log-level? 'info])]{ Initializes the file with the given @scheme[logger] and @scheme[current-log-level]. } @defmethod[#:mode public-final (->logger) logger?]{ Unwraps the logger associated with the file. } @defmethod*[#:mode public-final ([(log-level) log-level?] [(log-level [new-level log-level?]) void?])]{ Retrieves or changes the log level associated with the file. } @defmethod[#:mode override-final (open [context (is-a?/c server-context%)] [mode natural-number/c]) (values log-receiver? natural-number/c)]{ Opens the file, creating a log receiver as the I/O state and using a maximum I/O unit size of @scheme[(- (max-message-size) 24)]. } @defmethod[#:mode override-final (read [context (is-a?/c server-context%)] [i/o-state log-receiver?] [size natural-number/c] [offset natural-number/c]) (or/c bytes? eof-object?)]{ Reads a single log event from the log receiver. Blocks if no message is currently available. The log event is encoded using @scheme[write] to a bytes output port and truncated to fit into the I/O unit size. } } @defclass[server-hash-directory% (server-file:parent-mixin (server-file:stat-mixin server-directory%)) ()]{ Generic in-memory directory. @defconstructor/auto-super[([with-children (listof (and/c (is-a?/c server-file/stat<%>) (is-a?/c server-file/parent<%>))) null])]{ Initializes the directory with a list of files it contains. Specifying a non-null @scheme[with-children] constructor argument is functionally equivalent to adding every element of the list as an entry to the directory using the @method[server-hash-directory% add-child] method. } @defmethod[#:mode override-final (child [name string?]) (and/c (is-a?/c server-file/stat<%>) (is-a?/c server-file/parent<%>))]{ Retrieves a child from the directory. The method raises a filesystem exception with the message @scheme[ENOENT] if no child of the given @scheme[name] exists. } @defmethod[#:mode override-final (in-entries [context (is-a?/c server-context%)]) sequence?]{ Generates a sequence of @scheme[stat] directory entries by iterating over all files in the directory and asking each of them for their directory entry information by invoking @method[server-file<%> read-stat] on them. } @defmethod[#:mode public-final (add-child [child (and/c (is-a?/c server-file/stat<%>) (is-a?/c server-file/parent<%>))]) void?]{ Adds a new child to the directory. Adding a child also registers a name change listener on it that automatically updates the hash table mapping child names to children for the directory when a file is renamed or removes the child when it is deleted. } @defmethod[#:mode public-final (remove-child [child/name (or/c (and/c (is-a?/c server-file/stat<%>) (is-a?/c server-file/parent<%>)) string?)]) void?]{ Removes an existing child from the directory. @scheme[child/name] can either be the file to remove or its name. Removing a child also unregisters the name change listener of the directory. } @defmethod[#:mode override (create [context (is-a?/c server-context%)] [name string?] [perm natural-number/c] [mode natural-number/c]) (and/c (is-a?/c server-file/stat<%>) (is-a?/c server-file/parent<%>))]{ Creates a new child in the directory. This method can create subdirectories of type @scheme[server-hash-directory%] and regular files of type @scheme[server-bytes-file%]. } }