tcl−fuse − Tcl FUSE interface |
package require fuse fuse create fsname ?−getattr proc? ?−readdir proc? ?−open proc? ?−read proc? ?−readlink proc? ?−rename proc? ?−mknod proc? ?−mkdir proc? ?−unlink proc? ?−rmdir proc? ?−symlink proc? ?−link proc? ?−chmod proc? ?−chown proc? ?−truncate proc? ?−write proc? ?−statfs proc? ?−flush proc? ?−release proc? ?−fsync proc? ?−setxattr proc? ?−getxattr proc? ?−listxattr proc? ?−removexattr proc? ?−opendir proc? ?−releasedir proc? ?−fsyncdir proc? ?−destroy proc? ?−access proc? ?−create proc? ?−ftruncate proc? ?−fgetattr proc? ?−lock proc? ?−utimens proc? ?−bmap proc? ?−userdata list? fuse version fsname options... |
FUSE (File−System in User−Space) is a linux kernel subsystem and accompanying library that allows regular programs to implement a filesystem completely in userspace. The filesystem operations are received by the kernel via the standard Linux VFS interface, and are dispatched to a resident user process for execution (daemon). This has the following benefits: * it makes FS creation much easier * it keeps the kernel lightweight and less buggy * and removes the hassle of patching the kernel, or having the patch been accepted in the mainstream kernel. Tcl has a similar mechanism, the Tcl Virtual File System. Tcl VFS achieves a similar level of abstraction by attaching virtual file systems implemented in Tcl’s C API or Tcl (with TclVFS) on the viewâ of a Tcl interpreter. Consequently, Tcl scripts can leverage these virtual file systems, without any code modifications. They can access local and remote services that would otherwise require much more complex scripting. Unfortunately this virtual filesystems are only accessible to Tcl interpreters that have loaded them, and not to external processes. file−system â.PP This package is a Tcl interface to kernel’s FUSE subsystem, that allows for: * Easy creation of FUSE filesystems, written solely in Tcl. * Export of Tcl’s Virtual Filesystem layer via FUSE, so that non−Tcl processes can take advantage of it. The entry point to this functionality is a new Tcl command named fuse. This command creates new FUSE filesystems by establishing a binding between regular FUSE operations and Tcl handler procedures. For every FUSE filesystem it creates a new Tcl command, which can be used to mount it on a specific system directory. |
fuse create fsname ?−getattr proc? ?−readdir proc? ?−open proc? ?−read proc? ?−readlink proc? ?−rename proc? ?−mknod proc? ?−mkdir proc? ?−unlink proc? ?−rmdir proc? ?−symlink proc? ?−link proc? ?−chmod proc? ?−chown proc? ?−truncate proc? ?−write proc? ?−statfs proc? ?−flush proc? ?−release proc? ?−fsync proc? ?−setxattr proc? ?−getxattr proc? ?−listxattr proc? ?−removexattr proc? ?−opendir proc? ?−releasedir proc? ?−fsyncdir proc? ?−destroy proc? ?−access proc? ?−create proc? ?−ftruncate proc? ?−fgetattr proc? ?−lock proc? ?−utimens proc? ?−bmap proc? ?−userdata list? Creates a new FUSE filesystem, named fsname. The filesystem will call the specified Tcl procedures for each FUSE operation. The supported operations are: getattr, readdir, open, read, readlink, rename, mknod, mkdir, unlink, rmdir, symlink, link, chmod, chown, truncate, write, statfs, flush, release, fsync, setxattr, getxattr, listxattr, removexattr, opendir, releasedir, fsyncdir, destroy, access, create, ftruncate, fgetattr, lock, utimens, bmap, or userdata. Most of them are optional. The API and the role of each of these procedures is described hereafter. FUSE options: −d −o debug enable debug output (implies −f) −f foreground operation −s disable multi−threaded operation −o allow_other allow access to other users −o allow_root allow access to root −o nonempty allow mounts over non−empty file/dir −o default_permissions enable permission checking by kernel −o fsname=NAME set filesystem name −o subtype=NAME set filesystem type −o large_read issue large read requests (2.4 only) −o max_read=N set maximum size of read requests −o hard_remove immediate removal (don’t hide files) −o use_ino let filesystem set inode numbers −o readdir_ino try to fill in d_ino in readdir −o direct_io use direct I/O −o kernel_cache cache files in kernel −o [no]auto_cache enable caching based on modification times −o umask=M set file permissions (octal) −o uid=N set file owner −o gid=N set file group −o entry_timeout=T cache timeout for names (1.0s) −o negative_timeout=T cache timeout for deleted names (0.0s) −o attr_timeout=T cache timeout for attributes (1.0s) −o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout) −o intr allow requests to be interrupted −o intr_signal=NUM signal to send on interrupt (10) −o modules=M1[:M2...] names of modules to push onto filesystem stack −o max_write=N set maximum size of write requests −o max_readahead=N set maximum readahead −o async_read perform reads asynchronously (default) −o sync_read perform reads synchronously Module options: [subdir] −o subdir=DIR prepend this directory to all paths (mandatory) −o [no]rellinks transform absolute symlinks to relative [iconv] −o from_code=CHARSET original encoding of file names (default: UTF−8) −o to_code=CHARSET new encoding of the file names (default: UTF−8) fuse version Returns the version of the FUSE library. fsname options... Mounts the previously created FUSE filesystem on a target directory. Optional mount options can be specified. |
Checks whether the process requesting the operation can access the file pathname. This will be called for the access() system call. If the "default_permissions" mount option is given, this method is not called. If pathname is a symbolic link, it is dereferenced first. This operation is similar to access(2). The mode specifies the accessibility check(s) to be performed, and is either the value "exists", or a list consisting of any combination of the words "read", "write", "execute. "exists" tests for the existence of the file. The other three test for read, write, and execute permissions, respectively. Unlike in access(2), the check is done using the calling process’s effective UID/GUI. This can create some inconsistency while using the TclVFS in some usage scenarions. If the calling process is privileged (i.e., its real UID is zero), then an "exists" check is successful for a regular file if "execute" permission is enabled for any of the file owner, group, or other. Note: Generate the EACCES error when the requested privileges are not met. In case of multiple flags in $mask, all of them must be met. This method is not called under Linux kernel versions 2.4.x Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
||
mask |
Either "exists" or a list of "read", "write", "execute". |
Returns: Errors: |
Map block index within file to block index within device. This makes sense only for block device backed filesystems mounted with the "blkdev" option. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
blocksize |
Device blocksize. |
index |
Block index. |
Returns: Errors: |
Change permissions of a file, directory or other filesystem entity, similar to chmod(2). Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
||
perm |
A number describing the permissions for the file, such as 0777 but in decimal. |
Returns: Errors: |
Change ownership for a file, directory or other filesystem entity, similar to chown(2). Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
|||
owner |
The new owner of the file. |
|||
group |
The new group for the file. |
Returns: Errors: |
Create and open a file. If the file does not exist, first create it with the specified mode, and then open it. No NOCTTY flag will be passed to this procedure via the "flags" dictionary key of "fileinfo".. If this method is not implemented or under Linux kernel versions earlier than 2.6.15, the mknod and open procedures will be called instead. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
fileinfo |
File information for $path, including the open flags. |
perm |
A number describing the permissions for the file, such as 0777 but in decimal. |
Returns: Errors: |
Clean up filesystem. Called on filesystem exit. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
Returns: Errors: |
Get attributes from an open file, similar to fstat(2). This procedure is called instead of the getattr procedure if the file information is available. Currently this is only called after the create procedure if that is implemented (see above). Later it may be called for invocations of fstat too Retrieve file and directory information, similar to lstat(2). The "st_dev" and "st_blksize" fields are ignored. The "st_ino" field is ignored except if the "use_ino" mount option is given. The returned information include everything that "file stat" reports, and in the same format. Additionally, it supports the following dictionary keys that are not present in "file stat": rdev : The device ID, if the file is a device special file. blksize : The blocksize for filesystem I/O. blocks : The current number of blocks allocated for the file. If a key attribute is not present, the default is applied which is zero in all cases. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
path |
The file system entity to operate on. |
fileinfo Returns: Errors: |
Possibly flush cached data. ATTENTION: This is not equivalent to fsync(2). It’s not a request to sync dirty data. Flush is called on each close() of a file descriptor. So if a filesystem wants to return write errors in close() and the file has cached dirty data, this is a good place to write back data and return any errors. Since many applications ignore close() errors this is not always useful. NOTE: The flush procedure may be called more than once for each open. This happens if more than one file descriptor refers to an opened file due to dup(), dup2() or fork() calls. It is not possible to determine if a flush is final, so each flush should be treated equally. Multiple write−flush sequences are relatively rare, so this shouldn’t be a problem. Filesystems shouldn’t assume that flush will always be called after some writes, or that if will be called at all You must check that target is a file (not a directory). Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
fileinfo |
File information for $path. |
Returns: Errors: |
Synchronize file contents, similar to fsync(2). fsync can be performed on a directory, in addition to other filesystem entities. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
fileinfo |
File information for $path. |
datasync |
Boolean. If set then only data should be synced, not metadata. |
Returns: Errors: |
Synchronize directory contents. If the datasync parameter is non−zero, then only the user data should be flushed, not the meta data Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
fileinfo |
File information for $path. |
datasync |
Boolean. If set then only data should be synced, not metadata. |
Returns: Errors: |
Truncate an open file to a specified length, similar to ftruncate(2). You must check that target is not a directory. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
fileinfo |
File information for $path. |
size |
The new size of the file after truncation. |
Returns: Errors: |
Retrieve file and directory information, similar to lstat(2). The "st_dev" and "st_blksize" fields are ignored. The "st_ino" field is ignored except if the "use_ino" mount option is given. The returned information include everything that "file stat" reports, and in the same format. Additionally, it supports the following dictionary keys that are not present in "file stat": rdev : The device ID, if the file is a device special file. blksize : The blocksize for filesystem I/O. blocks : The current number of blocks allocated for the file. If a key attribute is not present, the default is applied which is zero in all cases. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
path |
The file system entity to operate on. |
Returns: Errors: |
Retrieve an extended attribute value, similar to getxattr(2). Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
||
name |
The name of the extended attribute. |
||
size |
The maximum size in bytes that the returned value can have, excluding the null−terminating character if the result is a string. |
Returns: Errors: |
Create a new hard link for a filesystem entity, similar to link(2). You must check that a filesystem entity does not already exist in the place where the symlink is requested to be created. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
||
link |
The location of the hardlink, relatively to $path. |
Returns: Errors: |
List extended attributes, similar to listxattr(2). Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
||
size |
The maximum size in bytes that the returned value can have. In the available size calculation you should subtruct the number of list items in the result, due to a null−terminating character for each one. |
Returns: Errors: |
Perform POSIX file locking operation, as those performed with fcntl(2). See: http://www.delorie.com/gnu/docs/glibc/libc_264.html The cmd argument will be either "getlock", "setlock" or "setlockblock". The meaning of each: getlock : To specify that it should get information about a lock. setlock : To specify that it should set or clear a lock. setlockblock : Like setlock, but causes the process to block (or wait) until the request can be specified. The argument lock is a dictionary with the following keys: type Specifies the type of the lock; one of "readlock", "writelock" or "unlock". origin Like the origin argument of seek(n). Accepts one of: "start", "current", or "end". offset This specifies the offset of the start of the region to which the lock applies, and is given in bytes relative to the point specified by origin. length This specifies the length of the region to be locked. A value of 0 is treated specially; it means the region extends to the end of the file. The origin key will always be set to "start". To check lock ownership, the lock_owner key of the fileinfo dictionary must be used, which holds the process ID of the process holding the lock. It is only significant when cmd argument is "getlock". For "getlock" operation, FUSE (on its own) will first check currently held locks, and if a conflicting lock is found it will return information without calling this procedure. This ensures, that for local locks the pid key is correctly filled in. The results may not be accurate in case of race conditions and in the presence of hard links, but it’s unlikly that an application would rely on accurate getlock results in these cases. If a conflicting lock is not found, this method will be called, and the filesystem may return a non−empty number that will become the owning pid. The pid of the process performing the locking operation can be found from dictionary context. NOTE: if this method is not implemented, the kernel will still allow file locking to work locally. Hence it is only interesting for network filesystems and similar. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
path |
The file system entity to operate on. |
fileinfo |
cmd |
Either "getlock", "setlock" or "setlockblock". |
||
lock |
A dictionary. Read description above for its |
constitution. Returns: Errors: |
Create a new directory, similar to mkdir(2). You must check that the target does not already exist (not necessarily as directory). Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
||
perm |
A number describing the permissions for the file, such as 0777 but in decimal. |
Returns: Errors: |
Create a new special or ordinary file, similar to mknod(2). This is called for creation of all non−directory, non−symlink nodes. If the FUSE filesystem defines a create operations, then for regular files that will be called instead. You must check that the target does not already exist. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
||
type |
The node type to create. One of the following: "file", "characterSpecial", "blockSpecial", "fifo", "socket". |
||
perm |
A number describing the permissions for the file, such as 0777 but in decimal. |
||
dev |
If the file type is "characterSpecial" or "blockSpecial" then dev specifies the major and minor numbers (in a list) of the newly created device special file; otherwise it is set to the empty list. |
Returns: Errors: |
Open a file, similar to open(2). No creation, or truncation flags (CREAT, EXCL, TRUNC) will be passed to this procedure via the "flags" dictionary key of "fileinfo", as well as the NOCTTY flag. Open should check if the operation is permitted for the given flags. Optionally open may also return an arbitrary filehandle (integer) that will be passed to all file operations that receive the "fileinfo" dictionary. The filesystem may also implement stateless file I/O and not return anything (empty list). Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
fileinfo |
File information for $path, including the open flags. |
Returns: Errors: |
Open directory, similar to opendir(3). This method should check if the open operation is permitted for the directory. This procedure can return an arbitrary file handle (integer) that will be present in fileinfo in other all other directory stream operations (readdir, releasedir, fsyncdir). The filesystem may also implement stateless directory I/O and not return anything (empty list), though that makes it impossible to implement standard conforming directory stream operations in case the contents of the directory can change between opendir and releasedir. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
fileinfo |
File information for $path. |
Returns: Errors: |
Read from a file, similar to read(2). You must check that the target is in fact a file. EOF is signified by returning less than the number of bytes requested, or the empty list. An exception to this is when the "direct_io" mount option is specified, in which case the return value of the read system call will reflect the return value of this operation. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
fileinfo |
File information for $path. |
size |
Number of bytes to read. |
|||
offset |
File offset (in bytes) to start reading from. |
Returns: Errors: |
Read the contents of a directory, similar to readdir(3). You must check that the target is in fact a directory. It is also legal for the target to be a symling that itself points to a directory. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
fileinfo |
File information for $path. |
Returns: Errors: |
Read the target of a symbolic link, similar to readlink(2). You must check that target is in fact a symlink. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
||
perm |
A number describing the permissions for the file, such as 0777 but in decimal. |
Returns: Errors: |
Release an open file. Release is called when there are no more references to an open file: all file descriptors are closed and all memory mappings are unmapped. For every open() call there will be exactly one release() call with the same flags and file descriptor. It is possible to have a file opened more than once, in which case only the last release will mean, that no more reads/writes will happen on the file. After a successfull execution of this procedure the fileinfo’s file_handle key is reset to 0, as we assume that any file handle has been closed. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
fileinfo |
File information for $path. |
Returns: Errors: |
Release an opened directory, similar to closedir(3). This method should check if the open operation is permitted for the directory. After a successfull execution of this procedure the fileinfo’s file_handle key is reset to 0, as we assume that any file handle has been closed. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
fileinfo |
File information for $path. |
Returns: Errors: |
Remove extended attributes, similar to removexattr(2). Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
|||
name |
The name of the extended attribute. |
Returns: Errors: |
Change the name and/or location of a file, similar to rename(2). You must check that the target is in fact a file. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
newpath |
The new location and name for the entity. |
Returns: Errors: |
Delete a directory, similar to rmdir(2). You must check that the target is a directory. The directory must be empty. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
Returns: Errors: |
Set an extended attribute, similar to setxattr(2). Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
|||
name |
The name of the extended attribute. |
|||
value |
The new value for the attribute. |
Returns: Errors: |
Get file system statistics, similar to statvfs(2). The result is a dictionary consisting of the keys: bsize file system block size frsize fragment size blocks size of fs in f_frsize units bfree free blocks bavail free blocks for non−root files inodes ffree free inodes favail free inodes for non−root fsid file system ID flag mount flags namemax maximum filename length Currently the frsize, favail, fsid and flag keys are ignored. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
path |
The file system entity to operate on. |
Returns: Errors: |
Create a symbolic link to a filesystem entity, similar to symlink(2). You must check that a filesystem entity does not already exist in the place where the symlink is requested to be created. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
||
link |
The location of the symlink, relatively to $path. |
Returns: Errors: |
Truncate a file to a specified length, similar to truncate(2). You must check that target is not a directory. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
|||
size |
The new size of the file after truncation. |
Returns: Errors: |
Delete a name and possibly the file it refers to from the filesystem. It is similar to unlink(2). The name can refer to a file, symlink, fifo, socket or device. You must check that the target is not a directory. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
Returns: Errors: |
Change the access and modification times of a file with second resolution. Utimens can be performed on a directory, in addition to other filesystem entities. Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
fileinfo |
File information for $path. |
atime |
Last access time in number of seconds since 1 January 1970. |
||
mtime |
Last modification time in number of seconds since 1 January 1970. |
Returns: Errors: |
Synchronize file contents, similar to write(2). Write should either write the exact number of bytes requested or generate an error. No partial writes should happen. An exception to this is when the "direct_io" mount option is specified (see read operation). Parameters: |
root |
The VFS root that is exported via FUSE. |
context |
A list with the uid, gid and pid of the process performing the operation. |
path |
The file system entity to operate on. |
fileinfo |
File information for $path. |
data |
The data to be written. Can be binary. |
|||
offset |
The offset in the file to write the data. |
Returns: Errors: |
#!/usr/bin/tclsh # A Tcl implementation of the "hello" filesystem used in FUSE documentation as an example. # See: http://fuse.sourceforge.net/helloworld.html # In this version the filesystem is automatically unmounted after 15 secs. package require fuse set hello_str "Hello World!\n" set hello_path "/hello" proc Getattr {context path} { global hello_str hello_path if {$path eq "/"} { return [dict create type directory mode 0755 nlinks 2] } elseif {$path eq $hello_path} { return [dict create mode 0444 nlinks 1 size [string length $hello_str]] } else { return −code error −errorcode [list POSIX ENOENT {}] } return ;# use default stat } proc Open {context path fileinfo} { global hello_path if {$path ne $hello_path} { return −code error −errorcode [list POSIX ENOENT {}] } if {"RDONLY" ni [dict get $fileinfo flags]} { return −code error −errorcode [list POSIX EACCES {}] } return ;# must be explicit. if a number is returned, then it becomes the file descriptor. } proc Readdir {context path fileinfo} { global hello_path if {$path ne "/"} { return −code error −errorcode [list POSIX ENOENT {}] } return [list "." ".." [string range $hello_path 1 end]] } proc Read {context path fileinfo size offset} { global hello_str hello_path if {$path ne $hello_path} { return −code error −errorcode [list POSIX ENOENT {}] } set len [string length $hello_str] if {$offset < $len} { if {$offset + $size > $len} { set size $len } return [string range $hello_str $offset $size] } return } fuse create hello −getattr Getattr −readdir Readdir −open Open −read Read hello examples/mnt −o nonempty set cnt 0 proc ping {} { global cnt if {$cnt == 15} { rename hello {}; return } puts [incr cnt] after 1000 ping } ping set forever 0 vwait forever |
Alexandros Stergiakis <sterg@kth.se> |
Copyright (C) 2008 Alexandros Stergiakis This program is free software: you can redistribute it
and/or This program is distributed in the hope that it will be
useful, You should have received a copy of the GNU General Public
License |