package plan9; /* * 9P2000 client * (a renaming of the StyxClient of styx-n-9p.googlecode.com) * * * TO DO * full walk (chunks of MAXWELEM, ..) * * Copyright © 2005 Vita Nuova Holdings Limited [C H Forsyth, forsyth@vitanuova.com] * Subject to the terms of the MIT-template (google for a copy) */ import java.nio.ByteBuffer; // not ideal, but will do for nowimport java.nio.channels.Channel;import java.nio.channels.ByteChannel;import java.util.StringTokenizer;import java.util.ArrayList;import java.util.concurrent.atomic.AtomicInteger; import java.io.EOFException;import java.io.IOException;import java.io.InterruptedIOException;import java.nio.channels.ClosedChannelException;import java.nio.channels.AsynchronousCloseException; import static plan9.Ninep.*; import plan9.lib.Misc;import plan9.lib.Strings; /** * NinepClient provides application-level access to a set of 9P servers, each accessed over a separate ByteChannel connection. * An instance of NinepClient provides state for a set of 9P connections. * Each 9P connection is represented by an instance of the inner class <i>Conn</i> ({@link NinepClient.Conn}), * associated with a ByteChannel connected to its server. Each 9P server has one or more named file systems, * including a default file system (named by the empty string). * <p> * A client <i>attaches</i> to one of those file systems. * If the server requires authentication, that <i>attach</i> request must be preceded by a * conversation on an authentication file (or "auth file") that establishes permission to request the attach. * A special <i>auth</i> request creates a new auth file. (The "file" is virtual and anonymous.) * If the server does not demand authentication, it will refuse an attempt to create an auth file. The usual * sequence is to attempt an <i>auth</i>, authenticate on the resulting auth file if it succeeds, * then <i>attach</i> after successful authentication but if the <i>auth</i> fails, * to attempt the <i>attach</i> anyway, without authentication. * (If the <i>auth</i> succeeds, but the conversation on the auth file fails, the <i>attach</i> will not succeed.) * For example, the class <i>NinepImport</i>, which encapsulates importing a file system exported by a 9P cpu server, * also encapsulates the <i>auth</i>-authentication-<i>attach</i> sequence in its simple <i>attach</i> operation. * <p> * The result of a successful attach is an instance of the inner class <i>FS</i> ({@link NinepClient.FS}), which provides file-system level * operations, including open, create, stat, wstat, and remove. The open and create operations return an instance * of the <i>FD</i> inner class ({@link NinepClient.FD}), allowing IO to a named file, compatible with the <i>ByteChannel</i> interface, * with extensions to support 9P's stat and wstat operations. */public class NinepClient { /** open for read */ public static final int OREAD = 0; /** open for write */ public static final int OWRITE = 1; /** open for read and write */ public static final int ORDWR = 2; /** open for execute, == read but check execute permission */ public static final int OEXEC = 3; /** OR'ed in to open mode: truncate file first */ public static final int OTRUNC = 16; /** OR'ed in to open mode: remove on close */ public static final int ORCLOSE = 64; /** OR'ed in to open mode: exclusive-create */ public static final int OEXCL = 0x1000; /** The offset in seek is an absolute offset from the start of the file */ static public final int SEEKSTART = 0; /** The offset in seek is a signed offset relative to the current file position */ static public final int SEEKRELA = 1; /** The offset in seek is a signed offset relative to the length of the file (ie, end of file) */ static public final int SEEKEND = 2; static public final String Enomem = "out of memory"; static public final String Eexists = "file already exists"; static public final String Eisdir = "file is a directory"; static public final String Enegoff = "negative i/o offset"; static public final String Etoosmall = "read or write too small"; static public final String Ebadusefd = "inappropriate use of fd"; static public final String Ehungup = "i/o on hungup channel"; static public final String Eversion = "version not established for mount channel"; static public final String Eintr = "interrupted"; static public final String Enotdir = "not a directory"; static public final String Edoesnotexist = "does not exist"; static public final String Emountrpc = "mount rpc error"; private static ThreadLocal<String> lasterror = new ThreadLocal<String>(); /** * Return the per-thread error string, which gives the error text from the most recent error. */ public final static String errstr() { return lasterror.get(); } /** * Set the per-thread error string. */ public final static void werrstr(String s) { lasterror.set(s); } final static void ioerror(String s) throws InterruptedIOException, IOException { lasterror.set(s); if(s != null && s.equals(Eintr)) throw new InterruptedIOException(s); throw new IOException(s); } final static void ioerror() throws IOException { ioerror(errstr()); } private static Ninep ninep = new Ninep(); private static Log log = LogFactory.logger(NinepClient.class); static String where(){ String s = ""; for(StackTraceElement e: Thread.currentThread().getStackTrace()) s += e + "\n"; return s; } /** * Create a new 9P client instance. * Given a NinepClient, the next step is to create a Conn, establishing a 9P connection on a given ByteChannel. * Conn, FS, and FD do all the real work. */ public NinepClient(){ settag(0); settag(Ninep.NOTAG); } private static class Request { Ninep.Tmsg t; Ninep.Rmsg r; Request flushed; long timeout; Request(Ninep.Tmsg t){ this.t = t; timeout = 0; } Request(Ninep.Tmsg t, Request old){ this.t = t; this.flushed = old; timeout = old.timeout; } void timelimit(long ms){ timeout = ms; } synchronized final void awaitreply() throws InterruptedException { if(timeout == 0){ while(r == null) this.wait(); }else{ long t0 = System.currentTimeMillis(); while(r == null){ this.wait(timeout); if(r != null) break; if(t0+timeout < System.currentTimeMillis()) throw new InterruptedException("file system operation timed-out"); } } } synchronized final void replied(Ninep.Rmsg reply){ if(this.t instanceof Ninep.Tflush && reply instanceof Ninep.Rerror) reply = ninep.new Rflush(reply.tag); // must not Rerror a Tflush this.r = reply; this.notify(); if(false && log.tracing()) log.trace("notify: %d %s %s", t.tag, t.mname(), reply.mname()); } synchronized final void replyerr(String why, boolean hangup){ Ninep.Rmsg reply; if(this.t instanceof Ninep.Tflush) reply = ninep.new Rflush(this.t.tag); // must not Rerror a Tflush else reply = ninep.new Rerror(this.t.tag, why, hangup); this.r = reply; this.notify(); } final void replyerr(String why){ replyerr(why, false); } } static class ReqEl { ReqEl next; Request r; ReqEl(Request r, ReqEl next){ this.r = r; this.next = next; } } static class RQ { ReqEl qh; ReqEl qt; String err; synchronized Request get() throws InterruptedException { ReqEl el; while((el = qh) == null && err == null) wait(); if(err != null) throw new InterruptedException(Eintr); qh = el.next; return el.r; } synchronized void put(Request r) throws InterruptedIOException, IOException { if(err != null) ioerror(err); ReqEl el = new ReqEl(r, null); if(qh == null){ qh = el; notify(); }else qt.next = el; qt = el; } synchronized void poison(String reason){ ReqEl el; if(err == null) err = reason; for(; (el = qh) != null; qh = el.next) el.r.replyerr(err, true); } } // record a set of outstanding Requests, in order to ensure replying to flushes in correct order on error private static class Requests { ReqEl set; // set of requests; tags are unique String err; // !=null: fail incoming requests with this error // add a new Request; ordering is unimportant synchronized final void add(Request req){ if(err == null) set = new ReqEl(req, set); else req.replied(ninep.new Rerror(req.t.tag, err, true)); } // find the Request corresponding to r's tag, and mark it done, notifying its author, and freeing the tag final void completed(Ninep.Rmsg r){ ReqEl el, prev; synchronized(this){ prev = null; for(el = set; el != null; el = el.next){ if(el.r.t.tag == r.tag){ if(prev != null) prev.next = el.next; else set = el.next; puttag(r.tag); el.r.replied(r); return; } prev = el; } } log.warn("unexpected reply tag %d type %d (%s)", r.tag, r.mtype(), r.mname()); //System.out.print("TAG: "+where()); } final void diagnose(String why){ log.warn("Ninep message error: "+why); } // shut down the channel, with reason, forcing all pending requests to fail, and answering flushes final void shutdown(String why){ if(log.debugging()) log.debug("shut down: "+why); synchronized(this){ if(err == null) // first error might be most accurate err = why; ReqEl el; while((el = set) != null){ set = el.next; el.r.replyerr(err, true); } } } } // thanks to Java's ByteChannel, which allows close to be repeated, we can't recycle fids just by recycling FDs // because there isn't any operation to mark a ByteChannel as no longer used // instead, separate Fid and FD private static class Fid { int fid; int inuse; Fid next; Fid(int v){ this.fid = v; this.inuse = 1; this.next = null; } synchronized Fid incref(){ inuse++; return this; } synchronized int decref(){ return --inuse; } synchronized boolean isfree(){ return inuse == 0; } } // recycle fids by recycling FDs private static class Fids { Fid avail; int fidgen = 0; int inuse = 0; boolean closing = false; synchronized Fid alloc() { Fid f; f = avail; if(f != null){ avail = f.next; f.next = null; f.inuse = 1; }else f = new Fid(++fidgen); inuse++; return f; } synchronized void free(Fid f) { if(f != null && f.isfree()){ f.next = avail; avail = f; inuse--; } } synchronized boolean close(){ if(inuse != 0){ closing = true; return false; } closing = false; return true; } synchronized boolean shouldclose(){ return inuse == 0 && closing; } } /** * Conn represents a 9P connection on a given ByteChannel. * <p> * It provides operations to initialise the connection (version), obtain an authentication file (auth), * attach to the root of a file system offered by the server (attach), and close the connection (close). * <p> * A successful attach returns an FS instance allowing operations on files by name. * Two of those operations, create and open, return an FD instance allowing IO on files. * Conn's auth operation also returns an FD instance, allowing IO on the resulting authentication file. * <p> * None of the operations regard failure as exceptional, and therefore do not throw exceptions. * Instead they return an error value suitable for the normal return type of the function. * A per-thread error string contains text giving the diagnostic for the most recent error. * The function errstr returns the error string. */ public class Conn { ByteChannel fd; int msize = 0; RQ writeq = new RQ(); // waiting to be sent to server Requests active = new Requests(); // sent to server; waiting for reply Fids fids = new Fids(); Object versioning = new Object(); // Tversion queue lock String versioned; boolean asyncflush = false; boolean closed = false; long optimer = 0; public Conn(ByteChannel fd){ this.fd = fd; (new Writer()).start(); (new Reader()).start(); } /** * Return the per-thread error string, which gives the error text from the most recent error. */ public String errstr(){ return NinepClient.this.errstr(); } /** * Set time limit in milliseconds for each 9P operation, until reset to zero. */ public void setTimeLimit(long ms){ optimer = ms; } /** * Optionally specify a maximum message size and protocol version. * By default, the protocol will use messages no larger than Ninep.MAXRPC, and negotiate the version "9P2000". * The 9P server can negotiate a message size limit that is still smaller. * <p> * Version does a 9P Tversion transaction. * @param msize desired maximum message size in bytes (if 0, use default Ninep.MAXRPC) * @param v desired protocol version (if null, use Ninep.VERSION, which is "9P2000") * @return protocol version negotiated, or null if the negotiation failed (see the error string) * @throws InterruptedIOException operation was interrupted (eg, by an alarm) * @throws ConnectionFailed 9P connection was shut down, by hangup or IO error */ public String version(int msize, String v) throws ConnectionFailed, InterruptedIOException { if(msize == 0) msize = Ninep.MAXRPC; if(v == null) v = Ninep.VERSION; synchronized(versioning){ // only one Tversion active at once on a connection if(versioned != null) return versioned; // already done; TO DO: check compatibility Ninep.Rversion r = (Ninep.Rversion)ninepreq(ninep.new Tversion(msize, v)); if(r == null) return null; // check compatibility if(r.msize < 256 || r.msize > 1024*1024){ werrstr("nonsense value of remote msize in version"); return null; } if(r.msize > msize){ werrstr("server tries to increase msize in version"); return null; } int n = r.version.length(); if(!v.substring(0, n).equals(r.version)){ werrstr("bad 9P version received from server"); return null; } this.msize = r.msize; this.versioned = r.version; } return this.versioned; } /** * Return a FD (file descriptor) that represents a channel to the authentication system on the server. * If the server requires authentication, a successful auth will return an FD instance that allows an * auth file on the server to be read and written to exchange authentication data with the server. * The aim is to authenticate <i>uname</i>'s access to the server's tree <i>aname</i>. *<p> * If the server does not require authentication, it will reply with an error, and auth will return null. * That does not represent a failure to authenticate, but rather that authentication is not required. * @param uname user name that will authenticate * @param aname name of server tree; use "" or null to get the default (main) tree * @return an FD instance connected to an authentication file; returns null if the server does not require authentication * @throws InterruptedIOException operation was interrupted (eg, by an alarm) * @throws ConnectionFailed 9P connection was shut down, by hangup or IO error */ public FD auth(String uname, String aname) throws ConnectionFailed, InterruptedIOException { if(versioned == null && version(0, null) == null) return null; FD fd = newfd(); Ninep.Rauth r = (Ninep.Rauth)ninepreq(ninep.new Tauth(fd.fid, uname, aname)); if(r == null){ fd.free(); return null; } fd.qid = r.aqid; fd.mode = Ninep.ORDWR; return fd; } /** * Attach as user <i>uname</i> to the root of the server's tree <i>aname</i>. * Return an FS instance giving access to that tree. * If the server does not require authentication, <i>afd</i> should be null. * If the server does require authentication, <i>afd</i> must be the result of a previous * <i>auth</i> call specifying the same <i>uname</i> and <i>aname</i>, * over which an authentication protocol has been run successfully. * @param afd file descriptor for authentication file, or null if none is needed * @param uname user name accessing the file system * @param aname access the tree with the given name (null or "" for default tree) * @throws InterruptedIOException operation was interrupted (eg, by an alarm) * @throws ConnectionFailed 9P connection was shut down, by hangup or IO error */ public FS attach(FD afd, String uname, String aname) throws ConnectionFailed, InterruptedIOException { if(versioned == null && version(0, null) == null)Lines 1–500 of 1763. Load more
package plan9; /* * 9P2000 client * (a renaming of the StyxClient of styx-n-9p.googlecode.com) * * * TO DO * full walk (chunks of MAXWELEM, ..) * * Copyright © 2005 Vita Nuova Holdings Limited [C H Forsyth, forsyth@vitanuova.com] * Subject to the terms of the MIT-template (google for a copy) */ import java.nio.ByteBuffer; // not ideal, but will do for now import java.nio.channels.Channel; import java.nio.channels.ByteChannel; import java.util.StringTokenizer; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicInteger; import java.io.EOFException; import java.io.IOException; import java.io.InterruptedIOException; import java.nio.channels.ClosedChannelException; import java.nio.channels.AsynchronousCloseException; import static plan9.Ninep.*; import plan9.lib.Misc; import plan9.lib.Strings; /** * NinepClient provides application-level access to a set of 9P servers, each accessed over a separate ByteChannel connection. * An instance of NinepClient provides state for a set of 9P connections. * Each 9P connection is represented by an instance of the inner class <i>Conn</i> ({@link NinepClient.Conn}), * associated with a ByteChannel connected to its server. Each 9P server has one or more named file systems, * including a default file system (named by the empty string). * <p> * A client <i>attaches</i> to one of those file systems. * If the server requires authentication, that <i>attach</i> request must be preceded by a * conversation on an authentication file (or "auth file") that establishes permission to request the attach. * A special <i>auth</i> request creates a new auth file. (The "file" is virtual and anonymous.) * If the server does not demand authentication, it will refuse an attempt to create an auth file. The usual * sequence is to attempt an <i>auth</i>, authenticate on the resulting auth file if it succeeds, * then <i>attach</i> after successful authentication but if the <i>auth</i> fails, * to attempt the <i>attach</i> anyway, without authentication. * (If the <i>auth</i> succeeds, but the conversation on the auth file fails, the <i>attach</i> will not succeed.) * For example, the class <i>NinepImport</i>, which encapsulates importing a file system exported by a 9P cpu server, * also encapsulates the <i>auth</i>-authentication-<i>attach</i> sequence in its simple <i>attach</i> operation. * <p> * The result of a successful attach is an instance of the inner class <i>FS</i> ({@link NinepClient.FS}), which provides file-system level * operations, including open, create, stat, wstat, and remove. The open and create operations return an instance * of the <i>FD</i> inner class ({@link NinepClient.FD}), allowing IO to a named file, compatible with the <i>ByteChannel</i> interface, * with extensions to support 9P's stat and wstat operations. */ public class NinepClient { /** open for read */ public static final int OREAD = 0; /** open for write */ public static final int OWRITE = 1; /** open for read and write */ public static final int ORDWR = 2; /** open for execute, == read but check execute permission */ public static final int OEXEC = 3; /** OR'ed in to open mode: truncate file first */ public static final int OTRUNC = 16; /** OR'ed in to open mode: remove on close */ public static final int ORCLOSE = 64; /** OR'ed in to open mode: exclusive-create */ public static final int OEXCL = 0x1000; /** The offset in seek is an absolute offset from the start of the file */ static public final int SEEKSTART = 0; /** The offset in seek is a signed offset relative to the current file position */ static public final int SEEKRELA = 1; /** The offset in seek is a signed offset relative to the length of the file (ie, end of file) */ static public final int SEEKEND = 2; static public final String Enomem = "out of memory"; static public final String Eexists = "file already exists"; static public final String Eisdir = "file is a directory"; static public final String Enegoff = "negative i/o offset"; static public final String Etoosmall = "read or write too small"; static public final String Ebadusefd = "inappropriate use of fd"; static public final String Ehungup = "i/o on hungup channel"; static public final String Eversion = "version not established for mount channel"; static public final String Eintr = "interrupted"; static public final String Enotdir = "not a directory"; static public final String Edoesnotexist = "does not exist"; static public final String Emountrpc = "mount rpc error"; private static ThreadLocal<String> lasterror = new ThreadLocal<String>(); /** * Return the per-thread error string, which gives the error text from the most recent error. */ public final static String errstr() { return lasterror.get(); } /** * Set the per-thread error string. */ public final static void werrstr(String s) { lasterror.set(s); } final static void ioerror(String s) throws InterruptedIOException, IOException { lasterror.set(s); if(s != null && s.equals(Eintr)) throw new InterruptedIOException(s); throw new IOException(s); } final static void ioerror() throws IOException { ioerror(errstr()); } private static Ninep ninep = new Ninep(); private static Log log = LogFactory.logger(NinepClient.class); static String where(){ String s = ""; for(StackTraceElement e: Thread.currentThread().getStackTrace()) s += e + "\n"; return s; } /** * Create a new 9P client instance. * Given a NinepClient, the next step is to create a Conn, establishing a 9P connection on a given ByteChannel. * Conn, FS, and FD do all the real work. */ public NinepClient(){ settag(0); settag(Ninep.NOTAG); } private static class Request { Ninep.Tmsg t; Ninep.Rmsg r; Request flushed; long timeout; Request(Ninep.Tmsg t){ this.t = t; timeout = 0; } Request(Ninep.Tmsg t, Request old){ this.t = t; this.flushed = old; timeout = old.timeout; } void timelimit(long ms){ timeout = ms; } synchronized final void awaitreply() throws InterruptedException { if(timeout == 0){ while(r == null) this.wait(); }else{ long t0 = System.currentTimeMillis(); while(r == null){ this.wait(timeout); if(r != null) break; if(t0+timeout < System.currentTimeMillis()) throw new InterruptedException("file system operation timed-out"); } } } synchronized final void replied(Ninep.Rmsg reply){ if(this.t instanceof Ninep.Tflush && reply instanceof Ninep.Rerror) reply = ninep.new Rflush(reply.tag); // must not Rerror a Tflush this.r = reply; this.notify(); if(false && log.tracing()) log.trace("notify: %d %s %s", t.tag, t.mname(), reply.mname()); } synchronized final void replyerr(String why, boolean hangup){ Ninep.Rmsg reply; if(this.t instanceof Ninep.Tflush) reply = ninep.new Rflush(this.t.tag); // must not Rerror a Tflush else reply = ninep.new Rerror(this.t.tag, why, hangup); this.r = reply; this.notify(); } final void replyerr(String why){ replyerr(why, false); } } static class ReqEl { ReqEl next; Request r; ReqEl(Request r, ReqEl next){ this.r = r; this.next = next; } } static class RQ { ReqEl qh; ReqEl qt; String err; synchronized Request get() throws InterruptedException { ReqEl el; while((el = qh) == null && err == null) wait(); if(err != null) throw new InterruptedException(Eintr); qh = el.next; return el.r; } synchronized void put(Request r) throws InterruptedIOException, IOException { if(err != null) ioerror(err); ReqEl el = new ReqEl(r, null); if(qh == null){ qh = el; notify(); }else qt.next = el; qt = el; } synchronized void poison(String reason){ ReqEl el; if(err == null) err = reason; for(; (el = qh) != null; qh = el.next) el.r.replyerr(err, true); } } // record a set of outstanding Requests, in order to ensure replying to flushes in correct order on error private static class Requests { ReqEl set; // set of requests; tags are unique String err; // !=null: fail incoming requests with this error // add a new Request; ordering is unimportant synchronized final void add(Request req){ if(err == null) set = new ReqEl(req, set); else req.replied(ninep.new Rerror(req.t.tag, err, true)); } // find the Request corresponding to r's tag, and mark it done, notifying its author, and freeing the tag final void completed(Ninep.Rmsg r){ ReqEl el, prev; synchronized(this){ prev = null; for(el = set; el != null; el = el.next){ if(el.r.t.tag == r.tag){ if(prev != null) prev.next = el.next; else set = el.next; puttag(r.tag); el.r.replied(r); return; } prev = el; } } log.warn("unexpected reply tag %d type %d (%s)", r.tag, r.mtype(), r.mname()); //System.out.print("TAG: "+where()); } final void diagnose(String why){ log.warn("Ninep message error: "+why); } // shut down the channel, with reason, forcing all pending requests to fail, and answering flushes final void shutdown(String why){ if(log.debugging()) log.debug("shut down: "+why); synchronized(this){ if(err == null) // first error might be most accurate err = why; ReqEl el; while((el = set) != null){ set = el.next; el.r.replyerr(err, true); } } } } // thanks to Java's ByteChannel, which allows close to be repeated, we can't recycle fids just by recycling FDs // because there isn't any operation to mark a ByteChannel as no longer used // instead, separate Fid and FD private static class Fid { int fid; int inuse; Fid next; Fid(int v){ this.fid = v; this.inuse = 1; this.next = null; } synchronized Fid incref(){ inuse++; return this; } synchronized int decref(){ return --inuse; } synchronized boolean isfree(){ return inuse == 0; } } // recycle fids by recycling FDs private static class Fids { Fid avail; int fidgen = 0; int inuse = 0; boolean closing = false; synchronized Fid alloc() { Fid f; f = avail; if(f != null){ avail = f.next; f.next = null; f.inuse = 1; }else f = new Fid(++fidgen); inuse++; return f; } synchronized void free(Fid f) { if(f != null && f.isfree()){ f.next = avail; avail = f; inuse--; } } synchronized boolean close(){ if(inuse != 0){ closing = true; return false; } closing = false; return true; } synchronized boolean shouldclose(){ return inuse == 0 && closing; } } /** * Conn represents a 9P connection on a given ByteChannel. * <p> * It provides operations to initialise the connection (version), obtain an authentication file (auth), * attach to the root of a file system offered by the server (attach), and close the connection (close). * <p> * A successful attach returns an FS instance allowing operations on files by name. * Two of those operations, create and open, return an FD instance allowing IO on files. * Conn's auth operation also returns an FD instance, allowing IO on the resulting authentication file. * <p> * None of the operations regard failure as exceptional, and therefore do not throw exceptions. * Instead they return an error value suitable for the normal return type of the function. * A per-thread error string contains text giving the diagnostic for the most recent error. * The function errstr returns the error string. */ public class Conn { ByteChannel fd; int msize = 0; RQ writeq = new RQ(); // waiting to be sent to server Requests active = new Requests(); // sent to server; waiting for reply Fids fids = new Fids(); Object versioning = new Object(); // Tversion queue lock String versioned; boolean asyncflush = false; boolean closed = false; long optimer = 0; public Conn(ByteChannel fd){ this.fd = fd; (new Writer()).start(); (new Reader()).start(); } /** * Return the per-thread error string, which gives the error text from the most recent error. */ public String errstr(){ return NinepClient.this.errstr(); } /** * Set time limit in milliseconds for each 9P operation, until reset to zero. */ public void setTimeLimit(long ms){ optimer = ms; } /** * Optionally specify a maximum message size and protocol version. * By default, the protocol will use messages no larger than Ninep.MAXRPC, and negotiate the version "9P2000". * The 9P server can negotiate a message size limit that is still smaller. * <p> * Version does a 9P Tversion transaction. * @param msize desired maximum message size in bytes (if 0, use default Ninep.MAXRPC) * @param v desired protocol version (if null, use Ninep.VERSION, which is "9P2000") * @return protocol version negotiated, or null if the negotiation failed (see the error string) * @throws InterruptedIOException operation was interrupted (eg, by an alarm) * @throws ConnectionFailed 9P connection was shut down, by hangup or IO error */ public String version(int msize, String v) throws ConnectionFailed, InterruptedIOException { if(msize == 0) msize = Ninep.MAXRPC; if(v == null) v = Ninep.VERSION; synchronized(versioning){ // only one Tversion active at once on a connection if(versioned != null) return versioned; // already done; TO DO: check compatibility Ninep.Rversion r = (Ninep.Rversion)ninepreq(ninep.new Tversion(msize, v)); if(r == null) return null; // check compatibility if(r.msize < 256 || r.msize > 1024*1024){ werrstr("nonsense value of remote msize in version"); return null; } if(r.msize > msize){ werrstr("server tries to increase msize in version"); return null; } int n = r.version.length(); if(!v.substring(0, n).equals(r.version)){ werrstr("bad 9P version received from server"); return null; } this.msize = r.msize; this.versioned = r.version; } return this.versioned; } /** * Return a FD (file descriptor) that represents a channel to the authentication system on the server. * If the server requires authentication, a successful auth will return an FD instance that allows an * auth file on the server to be read and written to exchange authentication data with the server. * The aim is to authenticate <i>uname</i>'s access to the server's tree <i>aname</i>. *<p> * If the server does not require authentication, it will reply with an error, and auth will return null. * That does not represent a failure to authenticate, but rather that authentication is not required. * @param uname user name that will authenticate * @param aname name of server tree; use "" or null to get the default (main) tree * @return an FD instance connected to an authentication file; returns null if the server does not require authentication * @throws InterruptedIOException operation was interrupted (eg, by an alarm) * @throws ConnectionFailed 9P connection was shut down, by hangup or IO error */ public FD auth(String uname, String aname) throws ConnectionFailed, InterruptedIOException { if(versioned == null && version(0, null) == null) return null; FD fd = newfd(); Ninep.Rauth r = (Ninep.Rauth)ninepreq(ninep.new Tauth(fd.fid, uname, aname)); if(r == null){ fd.free(); return null; } fd.qid = r.aqid; fd.mode = Ninep.ORDWR; return fd; } /** * Attach as user <i>uname</i> to the root of the server's tree <i>aname</i>. * Return an FS instance giving access to that tree. * If the server does not require authentication, <i>afd</i> should be null. * If the server does require authentication, <i>afd</i> must be the result of a previous * <i>auth</i> call specifying the same <i>uname</i> and <i>aname</i>, * over which an authentication protocol has been run successfully. * @param afd file descriptor for authentication file, or null if none is needed * @param uname user name accessing the file system * @param aname access the tree with the given name (null or "" for default tree) * @throws InterruptedIOException operation was interrupted (eg, by an alarm) * @throws ConnectionFailed 9P connection was shut down, by hangup or IO error */ public FS attach(FD afd, String uname, String aname) throws ConnectionFailed, InterruptedIOException { if(versioned == null && version(0, null) == null)