Download | Plain Text | No Line Numbers


  1. /*
  2.  * Copyright (c) 2010, Manuel Mausz. All rights reserved.
  3.  *
  4.  * Redistribution and use in source and binary forms, with or without
  5.  * modification, are permitted provided that the following conditions
  6.  * are met:
  7.  *
  8.  * - Redistributions of source code must retain the above copyright
  9.  * notice, this list of conditions and the following disclaimer.
  10.  *
  11.  * - Redistributions in binary form must reproduce the above copyright
  12.  * notice, this list of conditions and the following disclaimer in the
  13.  * documentation and/or other materials provided with the distribution.
  14.  *
  15.  * - The names of the authors may not be used to endorse or promote products
  16.  * derived from this software without specific prior written permission.
  17.  *
  18.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  19.  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  20.  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  21.  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  22.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  23.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  24.  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  25.  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  26.  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  27.  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  28.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29.  */
  30.  
  31. import static java.lang.System.err;
  32. import static java.lang.System.out;
  33.  
  34. import java.nio.ByteBuffer;
  35. import java.nio.channels.ServerSocketChannel;
  36. import java.nio.channels.SocketChannel;
  37.  
  38. import java.util.concurrent.ScheduledExecutorService;
  39. import java.util.concurrent.ScheduledFuture;
  40. import java.util.concurrent.ExecutorService;
  41. import java.util.concurrent.Executors;
  42. import java.util.concurrent.TimeUnit;
  43. import java.util.Arrays;
  44.  
  45. import java.net.*;
  46. import java.io.*;
  47.  
  48. /*
  49.  * Fileserver implementation for Lab#1 of DSLab WS10
  50.  * See angabe.pdf for details
  51.  *
  52.  * This code is not documented at all. This is volitional
  53.  *
  54.  * @author Manuel Mausz (0728348)
  55.  */
  56. public class Fileserver
  57. {
  58. public class ProxyConnection
  59. extends CommandNetwork
  60. implements Runnable
  61. {
  62. private final SocketChannel sock;
  63. private final String sharedFilesDir;
  64. private final ObjectInputStream oin;
  65. private final ObjectOutputStream oout;
  66.  
  67. ProxyConnection(SocketChannel sock, String sharedFilesDir)
  68. throws NoSuchMethodException, IOException
  69. {
  70. this.sock = sock;
  71. this.oout = new ObjectOutputStream(sock.socket().getOutputStream());
  72. this.oin = new ObjectInputStream(new BufferedInputStream(sock.socket().getInputStream()));
  73. this.sharedFilesDir = sharedFilesDir;
  74.  
  75. setOneCommandMode(true);
  76. cmdHandler.register("!list", this, "cmdList");
  77. cmdHandler.register("!download", this, "cmdDownload");
  78. cmdHandler.register("unknown", this, "cmdUnknown");
  79. }
  80.  
  81. /*------------------------------------------------------------------------*/
  82.  
  83. public void cmdList(String cmd, String[] args)
  84. throws IOException
  85. {
  86. if (args.length != 0)
  87. {
  88. err.println("Error: Invalid " + cmd + "-command paket from proxy. Ignoring...");
  89. return;
  90. }
  91.  
  92. File file = new File(sharedFilesDir);
  93. FilenameFilter filter = new FilenameFilter()
  94. {
  95. public boolean accept(File dir, String name)
  96. {
  97. File file = new File(dir, name);
  98. return (file.isFile() && file.canRead());
  99. }
  100. };
  101. Utils.sendOutput(oout, file.list(filter));
  102. }
  103.  
  104. /*------------------------------------------------------------------------*/
  105.  
  106. public void cmdDownload(String cmd, String[] args)
  107. throws IOException
  108. {
  109. if (args.length < 2 || args[1].length() <= 0)
  110. {
  111. err.println("Error: Invalid " + cmd + "-command paket from proxy. Ignoring...");
  112. return;
  113. }
  114.  
  115. long credits;
  116. if ((credits = Utils.parseHeaderNum(args, 1)) < 0)
  117. return;
  118.  
  119. File file = new File(sharedFilesDir, args[0]);
  120. if (!file.exists() || !file.getParent().equals(sharedFilesDir))
  121. {
  122. Utils.sendError(oout, "File '" + args[0] + "' does not exist");
  123. return;
  124. }
  125. if (!file.isFile())
  126. {
  127. Utils.sendError(oout, "File '" + args[0] + "' is not a file");
  128. return;
  129. }
  130. if (!file.canRead())
  131. {
  132. Utils.sendError(oout, "File '" + args[0] + "' is not readable");
  133. return;
  134. }
  135.  
  136. long filesize = file.length();
  137. if (credits < filesize)
  138. {
  139. Utils.sendOutput(oout, "You don't have enough credits");
  140. return;
  141. }
  142.  
  143. try
  144. {
  145. FileInputStream fin = new FileInputStream(file);
  146.  
  147. oout.writeUTF(cmd + " " + file.getName() + " " + filesize);
  148.  
  149. byte[] buffer = new byte[8 * 1024];
  150. int toread = buffer.length;
  151. while(filesize > 0)
  152. {
  153. if (filesize < toread)
  154. toread = (int) filesize;
  155. int count = fin.read(buffer, 0, toread);
  156. if (count == -1)
  157. throw new IOException("Error while reading from file");
  158. oout.write(buffer, 0, count);
  159. filesize -= count;
  160. }
  161. }
  162. catch(FileNotFoundException e)
  163. {
  164. Utils.sendError(oout, "File '" + args[0] + "' is not readable");
  165. }
  166. catch(IOException e)
  167. {
  168. err.println("Error during file transfer: " + e.getMessage());
  169. }
  170. }
  171.  
  172. /*------------------------------------------------------------------------*/
  173.  
  174. public void cmdUnknown(String cmd, String[] args)
  175. throws IOException
  176. {
  177. err.println("Error: Unknown data from proxy: " + cmd + " "
  178. + Utils.join(Arrays.asList(args), " "));
  179. Utils.sendError(oout, "Unknown command");
  180. }
  181.  
  182. /*------------------------------------------------------------------------*/
  183.  
  184. public void shutdown()
  185. {
  186. try
  187. {
  188. oout.flush();
  189. }
  190. catch(IOException e)
  191. {}
  192.  
  193. try
  194. {
  195. oin.close();
  196. }
  197. catch(IOException e)
  198. {}
  199.  
  200. try
  201. {
  202. oout.close();
  203. }
  204. catch(IOException e)
  205. {}
  206.  
  207. try
  208. {
  209. if (sock.isOpen())
  210. sock.close();
  211. }
  212. catch(IOException e)
  213. {}
  214. }
  215.  
  216. /*------------------------------------------------------------------------*/
  217.  
  218. public void run()
  219. {
  220. try
  221. {
  222. out.println("[" + Thread.currentThread().getId() + "] New connection from tcp:/"
  223. + sock.socket().getInetAddress() + ":" + sock.socket().getPort());
  224. run(oin);
  225. oout.flush();
  226. }
  227. catch(CommandHandler.Exception e)
  228. {
  229. err.println("Internal Error: " + e.getMessage());
  230. }
  231. catch(IOException e)
  232. {
  233. /* ignore that exception
  234.   * it's usually a closed connection from client so
  235.   * we can't do anything about it anyway
  236.   */
  237. }
  238.  
  239. out.println("[" + Thread.currentThread().getId() + "] Connection closed");
  240. shutdown();
  241. }
  242. }
  243.  
  244. /*==========================================================================*/
  245.  
  246. public class TCPSocketReader
  247. implements Runnable
  248. {
  249. private final ServerSocketChannel sschannel;
  250. private final String sharedFilesDir;
  251. private final Object mainLock;
  252. private final ExecutorService pool;
  253.  
  254. TCPSocketReader(ServerSocketChannel sschannel, String sharedFilesDir,
  255. Object mainLock)
  256. {
  257. this.sschannel = sschannel;
  258. this.sharedFilesDir = sharedFilesDir;
  259. this.mainLock = mainLock;
  260. this.pool = Executors.newCachedThreadPool();
  261. }
  262.  
  263. /*------------------------------------------------------------------------*/
  264.  
  265. public void run()
  266. {
  267. try
  268. {
  269. while(true)
  270. pool.execute(new ProxyConnection(sschannel.accept(), sharedFilesDir));
  271. }
  272. catch(NoSuchMethodException e)
  273. {
  274. err.println("Error: Unable to setup remote command handler");
  275. }
  276. catch(IOException e)
  277. {
  278. /* ignore that exception
  279.   * thread will shutdown and unlock the main thread
  280.   * which will shutdown the application
  281.   */
  282. }
  283.  
  284. pool.shutdown();
  285. try
  286. {
  287. if (!pool.awaitTermination(100, TimeUnit.MILLISECONDS))
  288. out.println("Trying to shutdown the proxy connections. This may take up to 15 seconds...");
  289. if (!pool.awaitTermination(5, TimeUnit.SECONDS))
  290. {
  291. pool.shutdownNow();
  292. if (!pool.awaitTermination(5, TimeUnit.SECONDS))
  293. err.println("Error: Proxy connections did not terminate. You may have to kill that appplication.");
  294. }
  295. }
  296. catch(InterruptedException e)
  297. {
  298. pool.shutdownNow();
  299. }
  300.  
  301. synchronized(mainLock)
  302. {
  303. mainLock.notify();
  304. }
  305. }
  306. }
  307.  
  308. /*==========================================================================*/
  309.  
  310. public class Interactive
  311. extends CommandInteractive
  312. implements Runnable
  313. {
  314. private final InputStream sin;
  315. private final Object mainLock;
  316.  
  317. Interactive(InputStream sin, Object mainLock)
  318. throws NoSuchMethodException
  319. {
  320. this.sin = sin;
  321. this.mainLock = mainLock;
  322.  
  323. cmdHandler.register("unknown", this, "cmdUnknown");
  324. cmdHandler.register("!exit", this, "cmdExit");
  325. }
  326.  
  327. /*------------------------------------------------------------------------*/
  328.  
  329. public void cmdUnknown(String cmd, String[] args)
  330. {
  331. err.println("Unknown command: " + cmd + " "
  332. + Utils.join(Arrays.asList(args), " "));
  333. }
  334.  
  335. /*------------------------------------------------------------------------*/
  336.  
  337. public void cmdExit(String cmd, String[] args)
  338. {
  339. stop();
  340. }
  341.  
  342. /*------------------------------------------------------------------------*/
  343.  
  344. public void printPrompt()
  345. {
  346. out.print(">: ");
  347. out.flush();
  348. }
  349.  
  350. /*------------------------------------------------------------------------*/
  351.  
  352. public void run()
  353. {
  354. try
  355. {
  356. run(sin);
  357. }
  358. catch(CommandHandler.Exception e)
  359. {
  360. err.println("Internal Error: " + e.getMessage());
  361. }
  362. catch (IOException e)
  363. {
  364. /* ignore that exception
  365.   * thread will shutdown and unlock the main thread
  366.   * which will shutdown the application
  367.   */
  368. }
  369.  
  370. synchronized(mainLock)
  371. {
  372. mainLock.notify();
  373. }
  374. }
  375. }
  376.  
  377. /*==========================================================================*/
  378.  
  379. public class PingTask
  380. implements Runnable
  381. {
  382. private final DatagramSocket sock;
  383. private final DatagramPacket packet;
  384. private final Object mainLock;
  385.  
  386. PingTask(DatagramSocket sock, DatagramPacket packet, Object mainLock)
  387. {
  388. this.sock = sock;
  389. this.packet = packet;
  390. this.mainLock = mainLock;
  391. }
  392.  
  393. /*------------------------------------------------------------------------*/
  394.  
  395. public void run()
  396. {
  397. try
  398. {
  399. sock.send(packet);
  400. }
  401. catch(IOException e)
  402. {
  403. err.println("Error while sending UDP ping packet: " + e.getMessage()
  404. + ". Terminating...");
  405. synchronized(mainLock)
  406. {
  407. mainLock.notify();
  408. }
  409. }
  410. }
  411. }
  412.  
  413. /*==========================================================================*/
  414.  
  415. private static String sharedFilesDir;
  416. private static String proxyHost;
  417. private static int tcpPort;
  418. private static int proxyUDPPort;
  419. private static int alivePeriod;
  420. private ScheduledExecutorService scheduler = null;
  421. private ServerSocketChannel sschannel = null;
  422. private DatagramSocket dsock = null;
  423. private Thread tTCPSocketReader = null;
  424. private Thread tInteractive = null;
  425. private InputStream stdin = null;
  426. private final Object mainLock = new Object();
  427.  
  428. /*--------------------------------------------------------------------------*/
  429.  
  430. public static void usage()
  431. throws Utils.Shutdown
  432. {
  433. out.println("Usage: Fileserver sharedFilesDir tcpPort proxyHost proxyUDPPort alivePeriod\n");
  434. out.println("sharedFilesDir\t...the directory that contains all the files clients can download");
  435. out.println("tcpPort\t\t...the port to be used for instantiating a ServerSocket");
  436. out.println("proxyHost\t...the host name or an IP address where the Proxy is running");
  437. out.println("proxyUDPPort\t...the UDP port where the Proxy is listening for fileserver datagrams");
  438. out.println("alivePeriod\t...the period in ms the fileserver needs to send an isAlive datagram to the Proxy");
  439.  
  440. // Java is some piece of crap which doesn't allow me to set exitcode w/o
  441. // using System.exit. Maybe someday Java will be a fully functional
  442. // programming language, but I wouldn't bet my money
  443. //System.exit(1);
  444. throw new Utils.Shutdown("FUCK YOU JAVA");
  445. }
  446.  
  447. /*--------------------------------------------------------------------------*/
  448.  
  449. public void bailout(String error)
  450. throws Utils.Shutdown
  451. {
  452. err.println("Error: " + error);
  453. shutdown();
  454.  
  455. // Java is some piece of crap which doesn't allow me to set exitcode w/o
  456. // using System.exit. Maybe someday Java will be a fully functional
  457. // programming language, but I wouldn't bet my money
  458. //System.exit(2);
  459. throw new Utils.Shutdown("FUCK YOU JAVA");
  460. }
  461.  
  462. /*--------------------------------------------------------------------------*/
  463.  
  464. public void parseArgs(String[] args)
  465. {
  466. if (args.length != 5)
  467. usage();
  468.  
  469. sharedFilesDir = args[0];
  470. File sharedir = new File(sharedFilesDir);
  471. if (!sharedir.isDirectory())
  472. bailout("sharedFilesDir '" + sharedFilesDir + "' is not a directory");
  473. if (!sharedir.canRead())
  474. bailout("sharedFilesDir '" + sharedFilesDir + "' is not readable");
  475.  
  476. try
  477. {
  478. tcpPort = Integer.parseInt(args[1]);
  479. if (tcpPort <= 0 || tcpPort > 65536)
  480. bailout("tcpPort must be a valid port number (1 - 65535)");
  481. }
  482. catch(NumberFormatException e)
  483. {
  484. bailout("tcpPort must be numeric");
  485. }
  486.  
  487. proxyHost = args[2];
  488. if (proxyHost.length() == 0)
  489. bailout("proxyHost is empty");
  490.  
  491. try
  492. {
  493. proxyUDPPort = Integer.parseInt(args[3]);
  494. if (proxyUDPPort <= 0 || proxyUDPPort > 65536)
  495. bailout("proxyUDPPort must be a valid port number (1 - 65535)");
  496. }
  497. catch(NumberFormatException e)
  498. {
  499. bailout("proxyUDPPort must be numeric");
  500. }
  501.  
  502. try
  503. {
  504. alivePeriod = Integer.parseInt(args[4]);
  505. if (alivePeriod <= 0)
  506. bailout("alivePeriod must be positive");
  507. }
  508. catch(NumberFormatException e)
  509. {
  510. bailout("alivePeriod must be numeric");
  511. }
  512. }
  513.  
  514. /*--------------------------------------------------------------------------*/
  515.  
  516. public void shutdown()
  517. {
  518. try
  519. {
  520. if (scheduler != null)
  521. {
  522. scheduler.shutdownNow();
  523. scheduler.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
  524. }
  525. }
  526. catch(InterruptedException e)
  527. {}
  528.  
  529. if (dsock != null)
  530. dsock.close();
  531.  
  532. try
  533. {
  534. if (sschannel != null)
  535. sschannel.close();
  536. }
  537. catch(IOException e)
  538. {}
  539.  
  540. try
  541. {
  542. if (tTCPSocketReader != null)
  543. tTCPSocketReader.join();
  544. }
  545. catch(InterruptedException e)
  546. {}
  547.  
  548. try
  549. {
  550. if (tInteractive != null)
  551. {
  552. tInteractive.interrupt();
  553. tInteractive.join();
  554. }
  555. }
  556. catch(InterruptedException e)
  557. {}
  558.  
  559. try
  560. {
  561. if (stdin != null)
  562. stdin.close();
  563. }
  564. catch(IOException e)
  565. {}
  566. }
  567.  
  568. /*--------------------------------------------------------------------------*/
  569.  
  570. public void run(String[] args)
  571. {
  572. parseArgs(args);
  573.  
  574. synchronized(mainLock)
  575. {
  576. try
  577. {
  578. dsock = new DatagramSocket();
  579. InetAddress proxyaddr = InetAddress.getByName(proxyHost);
  580. ByteBuffer buffer = ByteBuffer.allocate(4);
  581. buffer.putInt(tcpPort);
  582. DatagramPacket dpacket = new DatagramPacket(buffer.array(),
  583. buffer.array().length, proxyaddr, proxyUDPPort);
  584.  
  585. scheduler = Executors.newScheduledThreadPool(1);
  586. ScheduledFuture<?> pingTimer = scheduler.scheduleAtFixedRate(
  587. new PingTask(dsock, dpacket, mainLock),
  588. 0, alivePeriod, TimeUnit.MILLISECONDS);
  589. }
  590. catch(SocketException e)
  591. {
  592. bailout("Unable to create UDP Socket: " + e.getMessage());
  593. }
  594. catch(UnknownHostException e)
  595. {
  596. bailout("Unable to resolve hostname: " + e.getMessage());
  597. }
  598.  
  599. try
  600. {
  601. sschannel = ServerSocketChannel.open();
  602. sschannel.socket().bind(new InetSocketAddress(tcpPort));
  603. tTCPSocketReader = new Thread(new TCPSocketReader(sschannel,
  604. sharedFilesDir, mainLock));
  605. tTCPSocketReader.start();
  606. out.println("Listening on tcp:/" + sschannel.socket().getLocalSocketAddress());
  607. }
  608. catch(IOException e)
  609. {
  610. bailout("Unable to create TCP Socket: " + e.getMessage());
  611. }
  612.  
  613. try
  614. {
  615. InputStream stdin = java.nio.channels.Channels.newInputStream(
  616. new FileInputStream(FileDescriptor.in).getChannel());
  617. tInteractive = new Thread(new Interactive(stdin, mainLock));
  618. tInteractive.start();
  619. }
  620. catch(NoSuchMethodException e)
  621. {
  622. bailout("Unable to setup interactive command handler");
  623. }
  624.  
  625. out.println("Fileserver startup successful!");
  626. try
  627. {
  628. mainLock.wait();
  629. }
  630. catch(InterruptedException e)
  631. {
  632. /* if we get interrupted -> ignore */
  633. }
  634.  
  635. try
  636. {
  637. /* let the threads shutdown */
  638. Thread.sleep(100);
  639. }
  640. catch(InterruptedException e)
  641. {}
  642. }
  643.  
  644. if (tTCPSocketReader != null && !tTCPSocketReader.isAlive())
  645. bailout("Listening TCP socket closed unexpected. Terminating...");
  646.  
  647. shutdown();
  648. }
  649.  
  650. /*--------------------------------------------------------------------------*/
  651.  
  652. public static void main(String[] args)
  653. {
  654. try
  655. {
  656. Fileserver fserver = new Fileserver();
  657. fserver.run(args);
  658. }
  659. catch(Utils.Shutdown e)
  660. {}
  661. }
  662. }
  663.