View Javadoc

1   /*
2    * Copyright (c) 2005 The University of Reading
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    * 1. Redistributions of source code must retain the above copyright
9    *    notice, this list of conditions and the following disclaimer.
10   * 2. Redistributions in binary form must reproduce the above copyright
11   *    notice, this list of conditions and the following disclaimer in the
12   *    documentation and/or other materials provided with the distribution.
13   * 3. Neither the name of the University of Reading, nor the names of the
14   *    authors or contributors may be used to endorse or promote products
15   *    derived from this software without specific prior written permission.
16   * 
17   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20   * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   */
28  
29  package uk.ac.rdg.resc.jstyx.server;
30  
31  import org.apache.mina.common.IdleStatus;
32  import org.apache.mina.protocol.ProtocolHandler;
33  import org.apache.mina.protocol.ProtocolSession;
34  
35  import org.apache.log4j.Logger;
36  
37  import uk.ac.rdg.resc.jstyx.StyxUtils;
38  import uk.ac.rdg.resc.jstyx.StyxException;
39  
40  import uk.ac.rdg.resc.jstyx.types.DirEntry;
41  import uk.ac.rdg.resc.jstyx.types.Qid;
42  
43  import uk.ac.rdg.resc.jstyx.messages.*;
44  
45  /***
46   * ProtocolHandler listener for a StyxServer (replaces StyxServerSessionListener
47   * from Netty).
48   * @todo Should these methods be more thread-safe, i.e. should we make sure that
49   * each method that affects a StyxFile is synchronized on that StyxFile?
50   *
51   * @author Jon Blower
52   * $Revision: 604 $
53   * $Date: 2006-03-21 14:58:42 +0000 (Tue, 21 Mar 2006) $
54   * $Log$
55   * Revision 1.16  2006/03/21 14:58:42  jonblower
56   * Implemented clear-text password-based authentication and did some simple tests
57   *
58   * Revision 1.15  2006/03/21 09:06:15  jonblower
59   * Still implementing authentication
60   *
61   * Revision 1.14  2006/03/20 17:51:50  jonblower
62   * Adding authentication to base JStyx system
63   *
64   * Revision 1.13  2005/12/01 08:21:56  jonblower
65   * Fixed javadoc comments
66   *
67   * Revision 1.12  2005/11/03 17:09:27  jonblower
68   * Created more efficient RreadMessage that involves less copying of buffers (still reliable)
69   *
70   * Revision 1.11  2005/09/08 07:08:59  jonblower
71   * Removed "String user" from list of parameters to StyxFile.write()
72   *
73   * Revision 1.10  2005/05/19 14:46:51  jonblower
74   * Changed behaviour of StyxDirectory.createChild(): no longer adds file to namespace in this method
75   *
76   * Revision 1.9  2005/05/10 19:19:44  jonblower
77   * Added call to StyxMessage.dispose() in messageSent() callback
78   *
79   * Revision 1.8  2005/05/05 16:57:38  jonblower
80   * Updated MINA library to revision 168337 and changed code accordingly
81   *
82   * Revision 1.7  2005/04/28 08:11:15  jonblower
83   * Modified permissions handling in documentation directory of SGS
84   *
85   * Revision 1.6  2005/03/19 21:47:02  jonblower
86   * Further fixes relating to releasing ByteBuffers
87   *
88   * Revision 1.5  2005/03/16 22:16:43  jonblower
89   * Added Styx Grid Service classes to core module
90   *
91   * Revision 1.4  2005/03/16 17:56:24  jonblower
92   * Replaced use of java.nio.ByteBuffer with MINA's ByteBuffer to minimise copying of buffers
93   *
94   * Revision 1.3  2005/03/15 15:51:41  jonblower
95   * Removed hard limit on maximum message size
96   *
97   * Revision 1.2  2005/03/11 14:02:16  jonblower
98   * Merged MINA-Test_20059309 into main line of development
99   *
100  * Revision 1.1.2.4  2005/03/11 08:30:30  jonblower
101  * Moved to log4j logging system (from apache commons logging)
102  *
103  * Revision 1.1.2.3  2005/03/10 20:55:40  jonblower
104  * Removed references to Netty
105  *
106  * Revision 1.1.2.2  2005/03/10 11:55:00  jonblower
107  * Replaced StyxFile with StyxDirectory for root of server
108  *
109  * Revision 1.1.2.1  2005/03/10 08:10:41  jonblower
110  * Initial import
111  *
112  */
113 public class StyxServerProtocolHandler implements ProtocolHandler
114 {
115     private static final Logger log = Logger.getLogger(StyxServerProtocolHandler.class);
116     
117     private StyxDirectory root; // Root of the file tree
118     private StyxSecurityContext securityContext; // Security context
119     
120     public StyxServerProtocolHandler(StyxDirectory fileTreeRoot,
121         StyxSecurityContext securityContext)
122     {
123         this.root = fileTreeRoot;
124         this.securityContext = securityContext;
125     }
126     
127     /***
128      * Invoked when the session is created.  Initialize default socket
129      * parameters and user-defined attributes here.
130      */
131     public void sessionCreated( ProtocolSession session ) throws Exception
132     {
133         log.info( session.getRemoteAddress() + " CREATED" );
134     }
135     
136     public void sessionOpened(ProtocolSession  session )
137     {
138         log.info( session.getRemoteAddress() + " OPENED" );
139         session.setAttachment(new StyxSessionState(session));
140     }
141     
142     public void sessionClosed(ProtocolSession session )
143     {
144         StyxSessionState sessionState = (StyxSessionState)session.getAttachment();
145         sessionState.clunkAll();
146         sessionState.flushAll();
147         log.info( session.getRemoteAddress() + " CLOSED" );
148     }
149     
150     public void messageReceived(ProtocolSession session, Object message )
151     {
152         if (log.isDebugEnabled())
153         {
154             log.debug( session.getRemoteAddress() + " RCVD: " + message );
155         }
156         
157         // The cast is safe because we know we're only going to get StyxMessages
158         StyxMessage styxMessage = (StyxMessage)message;
159         
160         // Get the tag for the message
161         int tag = styxMessage.getTag();
162         
163         // Get the object representing the ProtocolSession state
164         StyxSessionState sessionState = (StyxSessionState)session.getAttachment();
165         
166         try
167         {
168             // Add the message's tag to the list of tags in use.
169             sessionState.addTag(tag); // this will throw an exception if the tag is in use
170             
171             if (message instanceof TversionMessage)
172             {
173                 replyVersion(session, sessionState, (TversionMessage)message, tag);
174             }
175             else if (message instanceof TauthMessage)
176             {
177                 replyAuth(session, sessionState, (TauthMessage)message, tag);
178             }
179             else if (message instanceof TattachMessage)
180             {
181                 replyAttach(session, sessionState, (TattachMessage)message, tag);
182             }
183             else if (message instanceof TflushMessage)
184             {
185                 replyFlush(session, sessionState, (TflushMessage)message, tag);
186             }
187             else if (message instanceof TwalkMessage)
188             {
189                 replyWalk(session, sessionState, (TwalkMessage)message, tag);
190             }
191             else if (message instanceof TopenMessage)
192             {
193                 replyOpen(session, sessionState, (TopenMessage)message, tag);
194             }
195             else if (message instanceof TcreateMessage)
196             {
197                 replyCreate(session, sessionState, (TcreateMessage)message, tag);
198             }
199             else if (message instanceof TreadMessage)
200             {
201                 replyRead(session, sessionState, (TreadMessage)message, tag);
202             }
203             else if (message instanceof TwriteMessage)
204             {
205                 replyWrite(session, sessionState, (TwriteMessage)message, tag);
206             }
207             else if (message instanceof TclunkMessage)
208             {
209                 replyClunk(session, sessionState, (TclunkMessage)message, tag);
210             }
211             else if (message instanceof TremoveMessage)
212             {
213                 replyRemove(session, sessionState, (TremoveMessage)message, tag);
214             }
215             else if (message instanceof TstatMessage)
216             {
217                 replyStat(session, sessionState, (TstatMessage)message, tag);
218             }
219             else if (message instanceof TwstatMessage)
220             {
221                 replyWstat(session, sessionState, (TwstatMessage)message, tag);
222             }
223             else
224             {
225                 throw new StyxException("Not a valid Styx Tmessage");
226             }
227         }
228         catch(TagInUseException tiue)
229         {
230             // Can't reply with an error to the client because we don't know 
231             // what tag to use! Simply log the error and don't reply.
232             log.error("tag " + tiue.getTag() + " already in use");
233         }
234         catch(FidNotFoundException fnfe)
235         {
236             reply(session, new RerrorMessage("fid " + fnfe.getFid() + 
237                 " not recognised"), tag);
238         }
239         catch(StyxException se)
240         {
241             reply(session, new RerrorMessage(se.getMessage()), tag);
242         }
243     }
244     
245     private void replyVersion(ProtocolSession session, StyxSessionState sessionState,
246         TversionMessage tVerMsg, int tag) throws StyxException
247     {
248         long proposedMessageSize = tVerMsg.getMaxMessageSize();
249         long finalMessageSize = Math.min(proposedMessageSize, StyxUtils.MAX_MESSAGE_SIZE);
250         if (!tVerMsg.getVersion().equals("9P2000"))
251         {
252             throw new StyxException("Error negotiating protocol version (must be 9P2000)");
253         }
254         // Reset the ProtocolSession (aborts all outstanding i/o, sets the message
255         // size and confirms that the version has been negotiated)
256         sessionState.resetSession(finalMessageSize);
257         reply(session, new RversionMessage(finalMessageSize, "9P2000"), tag);
258     }
259     
260     private void replyAuth(ProtocolSession session, StyxSessionState sessionState,
261         TauthMessage tAuthMsg, int tag) throws StyxException
262     {
263         // This message isn't used by Inferno (which uses a different
264         // authentication mechanism) but could be used in a different
265         // scheme
266         if (this.securityContext.supportsAuthentication())
267         {
268             // Check that the supplied fid isn't already in use
269             if (sessionState.fidInUse(tAuthMsg.getAfid()))
270             {
271                 throw new StyxException("Fid already in use");
272             }
273             // Create a new auth file for exchange of auth information
274             // This will throw a StyxException if the given username is not 
275             // recognized in the security context
276             AuthFile authFile = new AuthFile(this.securityContext, tAuthMsg.getUName());
277             sessionState.setUser(authFile.getUser());
278             // Associate this with the given fid
279             sessionState.associate(tAuthMsg.getAfid(), authFile);
280             // Reply with the Qid of this auth file
281             reply(session, new RauthMessage(authFile.getQid()), tag);
282         }
283         else
284         {
285             // Server does not support authentication
286             throw new StyxException("Authentication not supported");
287         }
288     }
289     
290     private void replyAttach(ProtocolSession session, StyxSessionState sessionState,
291         TattachMessage tAttMsg, int tag) throws StyxException
292     {
293         if (!sessionState.isVersionNegotiated())
294         {
295             throw new StyxException("Tversion not seen");
296         }
297         if (tAttMsg.getAfid() == StyxUtils.NOFID)
298         {
299             // Client is seeking an unauthenticated connection
300             if (this.securityContext.allowsAnonymousLogin())
301             {
302                 sessionState.setUser(this.securityContext.getAnonymousUser());
303             }
304             else
305             {
306                 throw new StyxException("Server does not allow anonymous logins");
307             }
308         }
309         else
310         {
311             // Client is seeking an authenticated connection
312             if (this.securityContext.supportsAuthentication())
313             {
314                 // Get the AuthFile associated with the provided afid
315                 // This will throw a StyxException if the auth file has not been created
316                 AuthFile authFile = (AuthFile)sessionState.getStyxFile(tAttMsg.getAfid());
317                 // See if the user is properly authenticated with this file
318                 if (!authFile.isAuthenticated(tAttMsg.getUname()))
319                 {
320                     throw new StyxException("User has not authenticated");
321                 }
322             }
323             else
324             {
325                 throw new StyxException("Authentication not supported");
326             }
327         }
328         // Check that the supplied fid isn't already in use
329         if (sessionState.fidInUse(tAttMsg.getFid()))
330         {
331             throw new StyxException("Fid already in use");
332         }
333         // Associate the fid with the root of the server
334         sessionState.associate(tAttMsg.getFid(), this.root);
335         // Ignore the aname part of the TattachMessage (TODO)
336         this.root.setLastAccessTime(StyxUtils.now());
337         reply(session, new RattachMessage(this.root.getQid()), tag);
338     }
339     
340     private void replyFlush(ProtocolSession session, StyxSessionState sessionState,
341         TflushMessage tFlushMsg, int tag) throws StyxException
342     {
343         sessionState.flushTag(tFlushMsg.getOldTag());
344         reply(session, new RflushMessage(), tag);
345     }
346     
347     private void replyWalk(ProtocolSession session, StyxSessionState sessionState,
348         TwalkMessage tWalkMsg, int tag) throws StyxException
349     {
350         if (tWalkMsg.getNumPathElements() > StyxUtils.MAXPATHELEMENTS)
351         {
352             throw new StyxException("Too many path elements in Twalk message");
353         }
354         long fid = tWalkMsg.getFid();
355         
356         // Have to check that this fid has not been opened by the client
357         StyxFile sf = sessionState.getStyxFile(fid);
358         StyxFileClient client = (StyxFileClient)sf.getClient(session, fid);
359         if (client != null)
360         {
361             throw new StyxException("cannot walk an open fid");
362         }
363         
364         long newFid = tWalkMsg.getNewFid();
365         String[] pathEls = tWalkMsg.getPathElements();
366 
367         if (newFid != fid)
368         {
369             // If the original and new fids are different, check that the 
370             // new fid isn't already in use
371             if (sessionState.fidInUse(newFid))
372             {
373                 throw new StyxException("Fid already in use");
374             }
375         }
376 
377         // Construct a blank RwalkMessage
378         RwalkMessage rWalkMsg = new RwalkMessage(new Qid[0]);
379 
380         for (int i = 0; i < pathEls.length; i++)
381         {
382             if (!(sf instanceof StyxDirectory))
383             {
384                 throw new StyxException(sf.getName() + " is not a directory");
385             }
386             if (!pathEls[i].equals(".."))
387             {
388                 if (!sessionState.checkExecute(sf))
389                 {
390                     // Only check file permissions if we're descending
391                     // the hierarchy
392                     throw new StyxException(sf.getName() + ": permission denied");
393                 }
394             }
395             sf.setLastAccessTime(StyxUtils.now());
396             sf = ((StyxDirectory)sf).getChild(pathEls[i]);
397             if (sf == null)
398             {
399                 if (i == 0)
400                 {
401                     throw new StyxException("file does not exist");
402                 }
403                 break;
404             }
405             // Note that this allows a client to get a fid representing
406             // the directory at the end of the walk, even if the client
407             // does not have execute permissions on that directory.
408             // Therefore in Inferno, a client could cd into the directory,
409             // but not read any of its contents.
410             rWalkMsg.putQid(sf.getQid());
411             // Refresh the directory (in the case of disk files, this will
412             // ensure that we have an up-to-date list of direct children
413             // of this directory)
414             sf.refresh();
415         }
416 
417         if (rWalkMsg.getNumSuccessfulWalks() == pathEls.length)
418         {
419             // The whole walk operation was successful. Associate the 
420             // new fid with the returned file
421             sessionState.associate(newFid, sf);
422         }
423         reply(session, rWalkMsg, tag);
424     }
425     
426     private void replyOpen(ProtocolSession session, StyxSessionState sessionState,
427         TopenMessage tOpenMsg, int tag) throws StyxException
428     {
429         StyxFile sf = sessionState.getStyxFile(tOpenMsg.getFid());
430         int mode = tOpenMsg.getMode();
431         sessionState.checkOpen(sf, mode);
432         // Now add this client to the file's list of connected clients
433         sf.addClient(new StyxFileClient(session, tOpenMsg.getFid(), mode));
434         if ((mode & StyxUtils.OTRUNC) == StyxUtils.OTRUNC)
435         {
436             // If we're opening with truncation, we must update the last modified
437             // time and user of the file.
438             sf.setLastModified(StyxUtils.now(), sessionState.getUser());
439         }
440         reply(session, new RopenMessage(sf.getQid(), sessionState.getIOUnit()), tag);
441     }
442     
443     private void replyCreate(ProtocolSession session, StyxSessionState sessionState,
444         TcreateMessage tCrtMsg, int tag) throws StyxException
445     {
446         StyxFile sf = sessionState.getStyxFile(tCrtMsg.getFid());
447         if (!(sf instanceof StyxDirectory))
448         {
449             throw new StyxException("can't create a file inside another file");
450         }
451         StyxDirectory dir = (StyxDirectory)sf;
452         // Check that the user has write permissions in this directory
453         if (!sessionState.checkWrite(dir))
454         {
455             throw new StyxException("permission denied: need write permissions in parent directory");
456         }
457         // Check the file type
458         long perm = tCrtMsg.getPerm();
459         boolean isDir = ((perm & StyxUtils.DMDIR) == StyxUtils.DMDIR);
460         boolean isAppOnly = ((perm & StyxUtils.DMAPPEND) == StyxUtils.DMAPPEND);
461         boolean isExclusive = ((perm & StyxUtils.DMEXCL) == StyxUtils.DMEXCL);
462         boolean isAuth = ((perm & StyxUtils.DMAUTH) == StyxUtils.DMAUTH);
463         if (isAuth)
464         {
465             // We won't allow auth files to be created via Styx messages
466             throw new StyxException("can't create a file of type DMAUTH");
467         }
468         // Get the low 9 bits of the permissions number (these low 9
469         // bits are the rwxrwxrwx file permissions)
470         int operm = (int)(tCrtMsg.getPerm() & 1023L);
471         // Get the real permissions for this file. This depends on the
472         // permissions of the parent directory.
473         int realPerm;
474         if (isDir)
475         {
476             realPerm = operm & (~0777 | (dir.getPermissions() & 0777));
477             // Directories must be opened with OREAD (no other bits set)
478             if (tCrtMsg.getMode() != StyxUtils.OREAD)
479             {
480                 throw new StyxException("when creating a directory, " +
481                     "must open with read permissions");
482             }
483         }
484         else
485         {
486             realPerm = operm & (~0666 | (dir.getPermissions() & 0666));
487         }
488         // Create the file
489         StyxFile newFile = dir.createChild(tCrtMsg.getFileName(), realPerm, 
490             isDir, isAppOnly, isExclusive);
491         // Add the new file to the directory tree
492         dir.addChild(newFile);
493         // Associate the new file with the given fid
494         sessionState.associate(tCrtMsg.getFid(), newFile);
495         // Now open the file with the given mode (note that the mode is
496         // not checked against the new permissions)
497         // TODO: if we are creating a directory, must we be asking for
498         // read permissions?
499         newFile.addClient(new StyxFileClient(session, tCrtMsg.getFid(), tCrtMsg.getMode()));
500         reply(session, new RcreateMessage(newFile.getQid(), sessionState.getIOUnit()), tag);
501     }
502     
503     private void replyRead(ProtocolSession session, StyxSessionState sessionState,
504         TreadMessage tReadMsg, int tag) throws StyxException
505     {
506         StyxFile sf = sessionState.getStyxFile(tReadMsg.getFid());
507         // check that file is open for reading by this client
508         StyxFileClient client = sf.getClient(session, tReadMsg.getFid());
509         if (client == null || !client.canRead())
510         {
511             throw new StyxException("File is not open for reading");
512         }
513         if (tReadMsg.getCount() > sessionState.getIOUnit())
514         {
515             throw new StyxException("can't request more than " +
516                 sessionState.getIOUnit() + " bytes in a single read");
517         }
518         // The last access time is set automatically by sf.replyRead()
519         sf.read(client, tReadMsg.getOffset().asLong(), tReadMsg.getCount(), tag);
520     }
521     
522     private void replyWrite(ProtocolSession session, StyxSessionState sessionState,
523         TwriteMessage tWriteMsg, int tag) throws StyxException
524     {         
525         StyxFile sf = sessionState.getStyxFile(tWriteMsg.getFid());
526         // check that file is open for writing by this client
527         StyxFileClient client = sf.getClient(session, tWriteMsg.getFid());
528         if (client == null || !client.canWrite())
529         {
530             throw new StyxException("File is not open for writing");
531         }
532         if (tWriteMsg.getCount() > sessionState.getIOUnit())
533         {
534             throw new StyxException("can't write more than " +
535                 sessionState.getIOUnit() + " bytes in a single operation");
536         }
537         boolean truncate = client.truncate();
538         long offset = tWriteMsg.getOffset().asLong();
539         // If this is an append-only file we ignore the specified offset and just
540         // write to the end of the file, without truncation. This relies on the
541         // getLength() method returning an accurate value.
542         if (sf.isAppendOnly())
543         {
544             offset = sf.getLength().asLong();
545             truncate = false;
546         }
547         // The last modified time is set automatically by sf.replyWrite()
548         sf.write(client, offset, tWriteMsg.getCount(), tWriteMsg.getData(),
549             truncate, tag);
550     }
551     
552     private void replyClunk(ProtocolSession session, StyxSessionState sessionState,
553         TclunkMessage tClkMsg, int tag) throws StyxException
554     {
555         sessionState.clunk(tClkMsg.getFid());
556         reply(session, new RclunkMessage(), tag);
557     }
558     
559     private void replyRemove(ProtocolSession session, StyxSessionState sessionState,
560         TremoveMessage tRmMsg, int tag) throws StyxException
561     {
562         StyxFile sf = sessionState.getStyxFile(tRmMsg.getFid());
563         synchronized (sf)
564         {
565             // A remove is considered as a clunk with the side-effect of
566             // removing the file if permissions allow
567             sessionState.clunk(tRmMsg.getFid());
568             // Check that the user has write permissions on the parent directory
569             // (N.B. user doesn't need write permissions on the file itself;
570             // see the Inferno manual entry for rm)
571             StyxFile parent = sf.getParent();
572             if (!sessionState.checkWrite(parent))
573             {
574                 // User doesn't have write permissions on the parent directory
575                 throw new StyxException("permission denied");
576             }
577             // If sf is a directory, it needs to be empty
578             if (sf instanceof StyxDirectory)
579             {
580                 StyxDirectory sd = (StyxDirectory)sf;
581                 if (sd.getNumChildren() != 0)
582                 {
583                     throw new StyxException("directory not empty");
584                 }
585             }
586             sf.remove();
587         }
588         // Set the last modified time and user of the parent directory
589         sf.getParent().setLastModified(StyxUtils.now(), sessionState.getUser());
590         reply(session, new RremoveMessage(), tag);
591     }
592     
593     private void replyStat(ProtocolSession session, StyxSessionState sessionState,
594         TstatMessage tStatMsg, int tag) throws StyxException
595     {
596         // Stat requests require no special permissions
597         StyxFile sf = sessionState.getStyxFile(tStatMsg.getFid());
598         // Return the information about this file/directory
599         reply(session, new RstatMessage(sf.getDirEntry()), tag);
600     }
601     
602     private void replyWstat(ProtocolSession session, StyxSessionState sessionState,
603         TwstatMessage tWstatMsg, int tag) throws StyxException
604     {
605         DirEntry stat = tWstatMsg.getDirEntry();
606         StyxFile sf = sessionState.getStyxFile(tWstatMsg.getFid());
607         synchronized (sf)
608         {                
609             // TODO: check to see if all the parts of the DirEntry are
610             // "do not change" flags - can we use this as a cue to refresh()
611             // the file?
612 
613             // Check if we are changing the name of the file
614             if (!stat.getFileName().equals(""))
615             {
616                 // Need write permissions on the parent directory to do this
617                 StyxDirectory dir = sf.getParent();
618                 if (!sessionState.checkWrite(dir))
619                 {
620                     throw new StyxException("write permissions required on parent directory to change file name");
621                 }
622                 // Check that a file with the same name doesn't exist
623                 if (dir.childExists(stat.getFileName()))
624                 {
625                     throw new StyxException("cannot rename file to the name of an existing file");
626                 }
627                 sf.checkSetName(stat.getFileName());
628             }
629 
630             // Check if we are changing the length of a file
631             if (stat.getFileLength().asLong() != -1)
632             {
633                 // Need write permission on the file to change length
634                 if (!sessionState.checkWrite(sf))
635                 {
636                     throw new StyxException("write permissions required to change file length");
637                 }
638                 sf.checkSetLength(stat.getFileLength());
639             }
640 
641             // Check if we are changing the mode of a file
642             if (stat.getMode() != StyxUtils.MAXUINT)
643             {
644                 // Must be the file owner to change the file mode.
645                 // TODO: allow the "group leader" also to change the file mode
646                 if (!sf.getOwner().equals(sessionState.getUser()))
647                 {
648                     throw new StyxException("must be owner to change file mode");
649                 }
650                 boolean setDir = ((stat.getMode() & StyxUtils.DMDIR) == StyxUtils.DMDIR);
651                 // Can't change the directory bit
652                 if (setDir != sf.isDirectory())
653                 {
654                     throw new StyxException("can't change a file to a directory or vice-versa");
655                 }
656                 sf.checkSetMode(stat.getMode());
657             }
658 
659             // Check if we are changing the last modification time of a file
660             if (stat.getLastModifiedTime() != StyxUtils.MAXUINT)
661             {                    
662                 // Must be the file owner to change the file mode.
663                 // TODO: allow the "group leader" also to change the file mode
664                 if (!sf.getOwner().equals(sessionState.getUser()))
665                 {
666                     throw new StyxException("must be owner to change last modified time");
667                 }
668                 sf.checkSetLastModifiedTime(stat.getLastModifiedTime());
669             }
670 
671             // Check if we are changing the group ID of a file
672             if (!stat.getGroup().equals(""))
673             {
674                 // Disallow changing of group ID for the moment
675                 throw new StyxException("can't change group ID on this server");
676             }
677 
678             // no other changes are possible
679             if (stat.getType() != StyxUtils.MAXUSHORT)
680             {
681                 throw new StyxException("can't change type");
682             }
683             if (stat.getDev() != StyxUtils.MAXUINT)
684             {
685                 throw new StyxException("can't change dev");
686             }
687             Qid qid = stat.getQid();
688             if (qid.getType() != StyxUtils.MAXUBYTE ||
689                 qid.getVersion() != StyxUtils.MAXUINT ||
690                 qid.getPath().asLong() != StyxUtils.MAXULONG)
691             {
692                 throw new StyxException("can't change qid");
693             }
694             if (stat.getLastAccessTime() != StyxUtils.MAXUINT)
695             {
696                 throw new StyxException("can't change last access time directly");
697             }
698             if (!stat.getOwner().equals(""))
699             {
700                 throw new StyxException("can't change file owner");
701             }
702             if (!stat.getLastModifiedBy().equals(""))
703             {
704                 throw new StyxException("can't directly change user who last modified the file");
705             }
706 
707             // Now we've checked all the permissions we can actually go ahead
708             // with all the changes
709             if (!stat.getFileName().equals(""))
710             {
711                 sf.setName(stat.getFileName());
712             } 
713             if (stat.getFileLength().asLong() != -1)
714             {
715                 // Set the file length; this might be rejected by the StyxFile itself
716                 sf.setLength(stat.getFileLength());
717             }
718             if (stat.getMode() != StyxUtils.MAXUINT)
719             {
720                 // the permissions do not depend on the parent directory's permissions
721                 // as they do in the Tcreate message
722                 sf.setMode(stat.getMode());
723             }
724             // Always set the last modified time/user for a directory even if
725             // not specifically requested
726             if (sf.isDirectory() || stat.getLastModifiedTime() != StyxUtils.MAXUINT)
727             {                    
728                 sf.setLastModified(stat.getLastModifiedTime(), sessionState.getUser());
729             }
730         }
731         
732         reply(session, new RwstatMessage(), tag);
733     }
734     
735     public void messageSent( ProtocolSession session, Object message )
736     {
737         if (log.isDebugEnabled())
738         {
739             log.debug( session.getRemoteAddress() + " SENT: " + message );
740         }
741         // Release any resources associated with the message.
742         if (message instanceof StyxMessage)
743         {
744             ((StyxMessage)message).dispose();
745         }
746     }
747     
748     public void sessionIdle( ProtocolSession session, IdleStatus status )
749     {
750         log.debug( session.getRemoteAddress() + " IDLE(" + status + ")" );
751         // Sessions are never disconnected if they are idle - is this OK?
752     }
753     
754     public void exceptionCaught( ProtocolSession session, Throwable cause )
755     {
756         if (log.isDebugEnabled())
757         {
758             cause.printStackTrace();
759         }
760         log.error( session.getRemoteAddress() + " EXCEPTION: " + cause.getMessage());
761         // close the connection on exceptional situation
762         session.close();
763     }
764     
765     /***
766      * Convenience method for sending a message back to the client.
767      */
768     public static void reply(ProtocolSession session, StyxMessage message, int tag)
769     {
770         StyxSessionState sessionState = (StyxSessionState)session.getAttachment();
771         synchronized(sessionState)
772         {
773             // If the tag has been flushed, don't reply
774             if (sessionState.tagInUse(tag))
775             {
776                 message.setTag(tag);
777                 session.write(message);
778                 sessionState.releaseTag(tag);
779             }
780         }
781     }
782     
783 }