View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.tftp;
19  
20  import java.io.BufferedInputStream;
21  import java.io.BufferedOutputStream;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.OutputStream;
29  import java.io.PrintStream;
30  import java.net.SocketTimeoutException;
31  import java.util.HashSet;
32  import java.util.Iterator;
33  
34  import org.apache.commons.net.io.FromNetASCIIOutputStream;
35  import org.apache.commons.net.io.ToNetASCIIInputStream;
36  
37  /**
38   * A fully multi-threaded tftp server. Can handle multiple clients at the same time. Implements RFC
39   * 1350 and wrapping block numbers for large file support.
40   *
41   * To launch, just create an instance of the class. An IOException will be thrown if the server
42   * fails to start for reasons such as port in use, port denied, etc.
43   *
44   * To stop, use the shutdown method.
45   *
46   * To check to see if the server is still running (or if it stopped because of an error), call the
47   * isRunning() method.
48   *
49   * By default, events are not logged to stdout/stderr. This can be changed with the
50   * setLog and setLogError methods.
51   *
52   * <p>
53   * Example usage is below:
54   *
55   * <code>
56   * public static void main(String[] args) throws Exception
57   *  {
58   *      if (args.length != 1)
59   *      {
60   *          System.out
61   *                  .println("You must provide 1 argument - the base path for the server to serve from.");
62   *          System.exit(1);
63   *      }
64   *
65   *      TFTPServer ts = new TFTPServer(new File(args[0]), new File(args[0]), GET_AND_PUT);
66   *      ts.setSocketTimeout(2000);
67   *
68   *      System.out.println("TFTP Server running.  Press enter to stop.");
69   *      new InputStreamReader(System.in).read();
70   *
71   *      ts.shutdown();
72   *      System.out.println("Server shut down.");
73   *      System.exit(0);
74   *  }
75   *
76   * </code>
77   *
78   *
79   * @author <A HREF="mailto:daniel.armbrust.list@gmail.com">Dan Armbrust</A>
80   * @since 2.0
81   */
82  
83  public class TFTPServer implements Runnable
84  {
85      private static final int DEFAULT_TFTP_PORT = 69;
86      public static enum ServerMode { GET_ONLY, PUT_ONLY, GET_AND_PUT; }
87  
88      private final HashSet<TFTPTransfer> transfers_ = new HashSet<TFTPTransfer>();
89      private volatile boolean shutdownServer = false;
90      private TFTP serverTftp_;
91      private File serverReadDirectory_;
92      private File serverWriteDirectory_;
93      private final int port_;
94      private Exception serverException = null;
95      private final ServerMode mode_;
96  
97      /* /dev/null output stream (default) */
98      private static final PrintStream nullStream = new PrintStream(
99              new OutputStream() {
100                 @Override
101                 public void write(int b){}
102                 @Override
103                 public void write(byte[] b) throws IOException {}
104                 }
105             );
106 
107     // don't have access to a logger api, so we will log to these streams, which
108     // by default are set to a no-op logger
109     private PrintStream log_;
110     private PrintStream logError_;
111 
112     private int maxTimeoutRetries_ = 3;
113     private int socketTimeout_;
114     private Thread serverThread;
115 
116 
117     /**
118      * Start a TFTP Server on the default port (69). Gets and Puts occur in the specified
119      * directories.
120      *
121      * The server will start in another thread, allowing this constructor to return immediately.
122      *
123      * If a get or a put comes in with a relative path that tries to get outside of the
124      * serverDirectory, then the get or put will be denied.
125      *
126      * GET_ONLY mode only allows gets, PUT_ONLY mode only allows puts, and GET_AND_PUT allows both.
127      * Modes are defined as int constants in this class.
128      *
129      * @param serverReadDirectory directory for GET requests
130      * @param serverWriteDirectory directory for PUT requests
131      * @param mode A value as specified above.
132      * @throws IOException if the server directory is invalid or does not exist.
133      */
134     public TFTPServer(File serverReadDirectory, File serverWriteDirectory, ServerMode mode)
135             throws IOException
136     {
137         this(serverReadDirectory, serverWriteDirectory, DEFAULT_TFTP_PORT, mode, null, null);
138     }
139 
140     /**
141      * Start a TFTP Server on the specified port. Gets and Puts occur in the specified directory.
142      *
143      * The server will start in another thread, allowing this constructor to return immediately.
144      *
145      * If a get or a put comes in with a relative path that tries to get outside of the
146      * serverDirectory, then the get or put will be denied.
147      *
148      * GET_ONLY mode only allows gets, PUT_ONLY mode only allows puts, and GET_AND_PUT allows both.
149      * Modes are defined as int constants in this class.
150      *
151      * @param serverReadDirectory directory for GET requests
152      * @param serverWriteDirectory directory for PUT requests
153      * @param mode A value as specified above.
154      * @param log Stream to write log message to. If not provided, uses System.out
155      * @param errorLog Stream to write error messages to. If not provided, uses System.err.
156      * @throws IOException if the server directory is invalid or does not exist.
157      */
158     public TFTPServer(File serverReadDirectory, File serverWriteDirectory, int port, ServerMode mode,
159             PrintStream log, PrintStream errorLog) throws IOException
160     {
161         port_ = port;
162         mode_ = mode;
163         log_ = (log == null ? nullStream: log);
164         logError_ = (errorLog == null ? nullStream : errorLog);
165         launch(serverReadDirectory, serverWriteDirectory);
166     }
167 
168     /**
169      * Set the max number of retries in response to a timeout. Default 3. Min 0.
170      *
171      * @param retries
172      */
173     public void setMaxTimeoutRetries(int retries)
174     {
175         if (retries < 0)
176         {
177             throw new RuntimeException("Invalid Value");
178         }
179         maxTimeoutRetries_ = retries;
180     }
181 
182     /**
183      * Get the current value for maxTimeoutRetries
184      */
185     public int getMaxTimeoutRetries()
186     {
187         return maxTimeoutRetries_;
188     }
189 
190     /**
191      * Set the socket timeout in milliseconds used in transfers. Defaults to the value here:
192      * http://commons.apache.org/net/apidocs/org/apache/commons/net/tftp/TFTP.html#DEFAULT_TIMEOUT
193      * (5000 at the time I write this) Min value of 10.
194      */
195     public void setSocketTimeout(int timeout)
196     {
197         if (timeout < 10)
198         {
199             throw new RuntimeException("Invalid Value");
200         }
201         socketTimeout_ = timeout;
202     }
203 
204     /**
205      * The current socket timeout used during transfers in milliseconds.
206      */
207     public int getSocketTimeout()
208     {
209         return socketTimeout_;
210     }
211 
212     /*
213      * start the server, throw an error if it can't start.
214      */
215     private void launch(File serverReadDirectory, File serverWriteDirectory) throws IOException
216     {
217         log_.println("Starting TFTP Server on port " + port_ + ".  Read directory: "
218                 + serverReadDirectory + " Write directory: " + serverWriteDirectory
219                 + " Server Mode is " + mode_);
220 
221         serverReadDirectory_ = serverReadDirectory.getCanonicalFile();
222         if (!serverReadDirectory_.exists() || !serverReadDirectory.isDirectory())
223         {
224             throw new IOException("The server read directory " + serverReadDirectory_
225                     + " does not exist");
226         }
227 
228         serverWriteDirectory_ = serverWriteDirectory.getCanonicalFile();
229         if (!serverWriteDirectory_.exists() || !serverWriteDirectory.isDirectory())
230         {
231             throw new IOException("The server write directory " + serverWriteDirectory_
232                     + " does not exist");
233         }
234 
235         serverTftp_ = new TFTP();
236 
237         // This is the value used in response to each client.
238         socketTimeout_ = serverTftp_.getDefaultTimeout();
239 
240         // we want the server thread to listen forever.
241         serverTftp_.setDefaultTimeout(0);
242 
243         serverTftp_.open(port_);
244 
245         serverThread = new Thread(this);
246         serverThread.setDaemon(true);
247         serverThread.start();
248     }
249 
250     @Override
251     protected void finalize() throws Throwable
252     {
253         shutdown();
254     }
255 
256     /**
257      * check if the server thread is still running.
258      *
259      * @return true if running, false if stopped.
260      * @throws Exception throws the exception that stopped the server if the server is stopped from
261      *             an exception.
262      */
263     public boolean isRunning() throws Exception
264     {
265         if (shutdownServer && serverException != null)
266         {
267             throw serverException;
268         }
269         return !shutdownServer;
270     }
271 
272 //    @Override
273     public void run()
274     {
275         try
276         {
277             while (!shutdownServer)
278             {
279                 TFTPPacket tftpPacket;
280 
281                 tftpPacket = serverTftp_.receive();
282 
283                 TFTPTransfer tt = new TFTPTransfer(tftpPacket);
284                 synchronized(transfers_)
285                 {
286                     transfers_.add(tt);
287                 }
288 
289                 Thread thread = new Thread(tt);
290                 thread.setDaemon(true);
291                 thread.start();
292             }
293         }
294         catch (Exception e)
295         {
296             if (!shutdownServer)
297             {
298                 serverException = e;
299                 logError_.println("Unexpected Error in TFTP Server - Server shut down! + " + e);
300             }
301         }
302         finally
303         {
304             shutdownServer = true; // set this to true, so the launching thread can check to see if it started.
305             if (serverTftp_ != null && serverTftp_.isOpen())
306             {
307                 serverTftp_.close();
308             }
309         }
310     }
311 
312     /**
313      * Stop the tftp server (and any currently running transfers) and release all opened network
314      * resources.
315      */
316     public void shutdown()
317     {
318         shutdownServer = true;
319 
320         synchronized(transfers_)
321         {
322             Iterator<TFTPTransfer> it = transfers_.iterator();
323             while (it.hasNext())
324             {
325                 it.next().shutdown();
326             }
327         }
328 
329         try
330         {
331             serverTftp_.close();
332         }
333         catch (RuntimeException e)
334         {
335             // noop
336         }
337 
338         try {
339             serverThread.join();
340         } catch (InterruptedException e) {
341             // we've done the best we could, return
342         }
343     }
344 
345     /*
346      * An instance of an ongoing transfer.
347      */
348     private class TFTPTransfer implements Runnable
349     {
350         private final TFTPPacket tftpPacket_;
351 
352         private boolean shutdownTransfer = false;
353 
354         TFTP transferTftp_ = null;
355 
356         public TFTPTransfer(TFTPPacket tftpPacket)
357         {
358             tftpPacket_ = tftpPacket;
359         }
360 
361         public void shutdown()
362         {
363             shutdownTransfer = true;
364             try
365             {
366                 transferTftp_.close();
367             }
368             catch (RuntimeException e)
369             {
370                 // noop
371             }
372         }
373 
374 //        @Override
375         public void run()
376         {
377             try
378             {
379                 transferTftp_ = new TFTP();
380 
381                 transferTftp_.beginBufferedOps();
382                 transferTftp_.setDefaultTimeout(socketTimeout_);
383 
384                 transferTftp_.open();
385 
386                 if (tftpPacket_ instanceof TFTPReadRequestPacket)
387                 {
388                     handleRead(((TFTPReadRequestPacket) tftpPacket_));
389                 }
390                 else if (tftpPacket_ instanceof TFTPWriteRequestPacket)
391                 {
392                     handleWrite((TFTPWriteRequestPacket) tftpPacket_);
393                 }
394                 else
395                 {
396                     log_.println("Unsupported TFTP request (" + tftpPacket_ + ") - ignored.");
397                 }
398             }
399             catch (Exception e)
400             {
401                 if (!shutdownTransfer)
402                 {
403                     logError_
404                             .println("Unexpected Error in during TFTP file transfer.  Transfer aborted. "
405                                     + e);
406                 }
407             }
408             finally
409             {
410                 try
411                 {
412                     if (transferTftp_ != null && transferTftp_.isOpen())
413                     {
414                         transferTftp_.endBufferedOps();
415                         transferTftp_.close();
416                     }
417                 }
418                 catch (Exception e)
419                 {
420                     // noop
421                 }
422                 synchronized(transfers_)
423                 {
424                     transfers_.remove(this);
425                 }
426             }
427         }
428 
429         /*
430          * Handle a tftp read request.
431          */
432         private void handleRead(TFTPReadRequestPacket trrp) throws IOException, TFTPPacketException
433         {
434             InputStream is = null;
435             try
436             {
437                 if (mode_ == ServerMode.PUT_ONLY)
438                 {
439                     transferTftp_.bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp
440                             .getPort(), TFTPErrorPacket.ILLEGAL_OPERATION,
441                             "Read not allowed by server."));
442                     return;
443                 }
444 
445                 try
446                 {
447                     is = new BufferedInputStream(new FileInputStream(buildSafeFile(
448                             serverReadDirectory_, trrp.getFilename(), false)));
449                 }
450                 catch (FileNotFoundException e)
451                 {
452                     transferTftp_.bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp
453                             .getPort(), TFTPErrorPacket.FILE_NOT_FOUND, e.getMessage()));
454                     return;
455                 }
456                 catch (Exception e)
457                 {
458                     transferTftp_.bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp
459                             .getPort(), TFTPErrorPacket.UNDEFINED, e.getMessage()));
460                     return;
461                 }
462 
463                 if (trrp.getMode() == TFTP.NETASCII_MODE)
464                 {
465                     is = new ToNetASCIIInputStream(is);
466                 }
467 
468                 byte[] temp = new byte[TFTPDataPacket.MAX_DATA_LENGTH];
469 
470                 TFTPPacket answer;
471 
472                 int block = 1;
473                 boolean sendNext = true;
474 
475                 int readLength = TFTPDataPacket.MAX_DATA_LENGTH;
476 
477                 TFTPDataPacket lastSentData = null;
478 
479                 // We are reading a file, so when we read less than the
480                 // requested bytes, we know that we are at the end of the file.
481                 while (readLength == TFTPDataPacket.MAX_DATA_LENGTH && !shutdownTransfer)
482                 {
483                     if (sendNext)
484                     {
485                         readLength = is.read(temp);
486                         if (readLength == -1)
487                         {
488                             readLength = 0;
489                         }
490 
491                         lastSentData = new TFTPDataPacket(trrp.getAddress(), trrp.getPort(), block,
492                                 temp, 0, readLength);
493                         transferTftp_.bufferedSend(lastSentData);
494                     }
495 
496                     answer = null;
497 
498                     int timeoutCount = 0;
499 
500                     while (!shutdownTransfer
501                             && (answer == null || !answer.getAddress().equals(trrp.getAddress()) || answer
502                                     .getPort() != trrp.getPort()))
503                     {
504                         // listen for an answer.
505                         if (answer != null)
506                         {
507                             // The answer that we got didn't come from the
508                             // expected source, fire back an error, and continue
509                             // listening.
510                             log_.println("TFTP Server ignoring message from unexpected source.");
511                             transferTftp_.bufferedSend(new TFTPErrorPacket(answer.getAddress(),
512                                     answer.getPort(), TFTPErrorPacket.UNKNOWN_TID,
513                                     "Unexpected Host or Port"));
514                         }
515                         try
516                         {
517                             answer = transferTftp_.bufferedReceive();
518                         }
519                         catch (SocketTimeoutException e)
520                         {
521                             if (timeoutCount >= maxTimeoutRetries_)
522                             {
523                                 throw e;
524                             }
525                             // didn't get an ack for this data. need to resend
526                             // it.
527                             timeoutCount++;
528                             transferTftp_.bufferedSend(lastSentData);
529                             continue;
530                         }
531                     }
532 
533                     if (answer == null || !(answer instanceof TFTPAckPacket))
534                     {
535                         if (!shutdownTransfer)
536                         {
537                             logError_
538                                     .println("Unexpected response from tftp client during transfer ("
539                                             + answer + ").  Transfer aborted.");
540                         }
541                         break;
542                     }
543                     else
544                     {
545                         // once we get here, we know we have an answer packet
546                         // from the correct host.
547                         TFTPAckPacket ack = (TFTPAckPacket) answer;
548                         if (ack.getBlockNumber() != block)
549                         {
550                             /*
551                              * The origional tftp spec would have called on us to resend the
552                              * previous data here, however, that causes the SAS Syndrome.
553                              * http://www.faqs.org/rfcs/rfc1123.html section 4.2.3.1 The modified
554                              * spec says that we ignore a duplicate ack. If the packet was really
555                              * lost, we will time out on receive, and resend the previous data at
556                              * that point.
557                              */
558                             sendNext = false;
559                         }
560                         else
561                         {
562                             // send the next block
563                             block++;
564                             if (block > 65535)
565                             {
566                                 // wrap the block number
567                                 block = 0;
568                             }
569                             sendNext = true;
570                         }
571                     }
572                 }
573             }
574             finally
575             {
576                 try
577                 {
578                     if (is != null)
579                     {
580                         is.close();
581                     }
582                 }
583                 catch (IOException e)
584                 {
585                     // noop
586                 }
587             }
588         }
589 
590         /*
591          * handle a tftp write request.
592          */
593         private void handleWrite(TFTPWriteRequestPacket twrp) throws IOException,
594                 TFTPPacketException
595         {
596             OutputStream bos = null;
597             try
598             {
599                 if (mode_ == ServerMode.GET_ONLY)
600                 {
601                     transferTftp_.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp
602                             .getPort(), TFTPErrorPacket.ILLEGAL_OPERATION,
603                             "Write not allowed by server."));
604                     return;
605                 }
606 
607                 int lastBlock = 0;
608                 String fileName = twrp.getFilename();
609 
610                 try
611                 {
612                     File temp = buildSafeFile(serverWriteDirectory_, fileName, true);
613                     if (temp.exists())
614                     {
615                         transferTftp_.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp
616                                 .getPort(), TFTPErrorPacket.FILE_EXISTS, "File already exists"));
617                         return;
618                     }
619                     bos = new BufferedOutputStream(new FileOutputStream(temp));
620 
621                     if (twrp.getMode() == TFTP.NETASCII_MODE)
622                     {
623                         bos = new FromNetASCIIOutputStream(bos);
624                     }
625                 }
626                 catch (Exception e)
627                 {
628                     transferTftp_.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp
629                             .getPort(), TFTPErrorPacket.UNDEFINED, e.getMessage()));
630                     return;
631                 }
632 
633                 TFTPAckPacket lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), 0);
634                 transferTftp_.bufferedSend(lastSentAck);
635 
636                 while (true)
637                 {
638                     // get the response - ensure it is from the right place.
639                     TFTPPacket dataPacket = null;
640 
641                     int timeoutCount = 0;
642 
643                     while (!shutdownTransfer
644                             && (dataPacket == null
645                                     || !dataPacket.getAddress().equals(twrp.getAddress()) || dataPacket
646                                     .getPort() != twrp.getPort()))
647                     {
648                         // listen for an answer.
649                         if (dataPacket != null)
650                         {
651                             // The data that we got didn't come from the
652                             // expected source, fire back an error, and continue
653                             // listening.
654                             log_.println("TFTP Server ignoring message from unexpected source.");
655                             transferTftp_.bufferedSend(new TFTPErrorPacket(dataPacket.getAddress(),
656                                     dataPacket.getPort(), TFTPErrorPacket.UNKNOWN_TID,
657                                     "Unexpected Host or Port"));
658                         }
659 
660                         try
661                         {
662                             dataPacket = transferTftp_.bufferedReceive();
663                         }
664                         catch (SocketTimeoutException e)
665                         {
666                             if (timeoutCount >= maxTimeoutRetries_)
667                             {
668                                 throw e;
669                             }
670                             // It didn't get our ack. Resend it.
671                             transferTftp_.bufferedSend(lastSentAck);
672                             timeoutCount++;
673                             continue;
674                         }
675                     }
676 
677                     if (dataPacket != null && dataPacket instanceof TFTPWriteRequestPacket)
678                     {
679                         // it must have missed our initial ack. Send another.
680                         lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), 0);
681                         transferTftp_.bufferedSend(lastSentAck);
682                     }
683                     else if (dataPacket == null || !(dataPacket instanceof TFTPDataPacket))
684                     {
685                         if (!shutdownTransfer)
686                         {
687                             logError_
688                                     .println("Unexpected response from tftp client during transfer ("
689                                             + dataPacket + ").  Transfer aborted.");
690                         }
691                         break;
692                     }
693                     else
694                     {
695                         int block = ((TFTPDataPacket) dataPacket).getBlockNumber();
696                         byte[] data = ((TFTPDataPacket) dataPacket).getData();
697                         int dataLength = ((TFTPDataPacket) dataPacket).getDataLength();
698                         int dataOffset = ((TFTPDataPacket) dataPacket).getDataOffset();
699 
700                         if (block > lastBlock || (lastBlock == 65535 && block == 0))
701                         {
702                             // it might resend a data block if it missed our ack
703                             // - don't rewrite the block.
704                             bos.write(data, dataOffset, dataLength);
705                             lastBlock = block;
706                         }
707 
708                         lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), block);
709                         transferTftp_.bufferedSend(lastSentAck);
710                         if (dataLength < TFTPDataPacket.MAX_DATA_LENGTH)
711                         {
712                             // end of stream signal - The tranfer is complete.
713                             bos.close();
714 
715                             // But my ack may be lost - so listen to see if I
716                             // need to resend the ack.
717                             for (int i = 0; i < maxTimeoutRetries_; i++)
718                             {
719                                 try
720                                 {
721                                     dataPacket = transferTftp_.bufferedReceive();
722                                 }
723                                 catch (SocketTimeoutException e)
724                                 {
725                                     // this is the expected route - the client
726                                     // shouldn't be sending any more packets.
727                                     break;
728                                 }
729 
730                                 if (dataPacket != null
731                                         && (!dataPacket.getAddress().equals(twrp.getAddress()) || dataPacket
732                                                 .getPort() != twrp.getPort()))
733                                 {
734                                     // make sure it was from the right client...
735                                     transferTftp_
736                                             .bufferedSend(new TFTPErrorPacket(dataPacket
737                                                     .getAddress(), dataPacket.getPort(),
738                                                     TFTPErrorPacket.UNKNOWN_TID,
739                                                     "Unexpected Host or Port"));
740                                 }
741                                 else
742                                 {
743                                     // This means they sent us the last
744                                     // datapacket again, must have missed our
745                                     // ack. resend it.
746                                     transferTftp_.bufferedSend(lastSentAck);
747                                 }
748                             }
749 
750                             // all done.
751                             break;
752                         }
753                     }
754                 }
755             }
756             finally
757             {
758                 if (bos != null)
759                 {
760                     bos.close();
761                 }
762             }
763         }
764 
765         /*
766          * Utility method to make sure that paths provided by tftp clients do not get outside of the
767          * serverRoot directory.
768          */
769         private File buildSafeFile(File serverDirectory, String fileName, boolean createSubDirs)
770                 throws IOException
771         {
772             File temp = new File(serverDirectory, fileName);
773             temp = temp.getCanonicalFile();
774 
775             if (!isSubdirectoryOf(serverDirectory, temp))
776             {
777                 throw new IOException("Cannot access files outside of tftp server root.");
778             }
779 
780             // ensure directory exists (if requested)
781             if (createSubDirs)
782             {
783                 createDirectory(temp.getParentFile());
784             }
785 
786             return temp;
787         }
788 
789         /*
790          * recursively create subdirectories
791          */
792         private void createDirectory(File file) throws IOException
793         {
794             File parent = file.getParentFile();
795             if (parent == null)
796             {
797                 throw new IOException("Unexpected error creating requested directory");
798             }
799             if (!parent.exists())
800             {
801                 // recurse...
802                 createDirectory(parent);
803             }
804 
805             if (parent.isDirectory())
806             {
807                 if (file.isDirectory())
808                 {
809                     return;
810                 }
811                 boolean result = file.mkdir();
812                 if (!result)
813                 {
814                     throw new IOException("Couldn't create requested directory");
815                 }
816             }
817             else
818             {
819                 throw new IOException(
820                         "Invalid directory path - file in the way of requested folder");
821             }
822         }
823 
824         /*
825          * recursively check to see if one directory is a parent of another.
826          */
827         private boolean isSubdirectoryOf(File parent, File child)
828         {
829             File childsParent = child.getParentFile();
830             if (childsParent == null)
831             {
832                 return false;
833             }
834             if (childsParent.equals(parent))
835             {
836                 return true;
837             }
838             else
839             {
840                 return isSubdirectoryOf(parent, childsParent);
841             }
842         }
843     }
844 
845     /**
846      * Set the stream object to log debug / informational messages. By default, this is a no-op
847      *
848      * @param log
849      */
850     public void setLog(PrintStream log)
851     {
852         this.log_ = log;
853     }
854 
855     /**
856      * Set the stream object to log error messsages. By default, this is a no-op
857      *
858      * @param logError
859      */
860     public void setLogError(PrintStream logError)
861     {
862         this.logError_ = logError;
863     }
864 }