summaryrefslogtreecommitdiff
path: root/cxmanage_api/tftp.py
diff options
context:
space:
mode:
Diffstat (limited to 'cxmanage_api/tftp.py')
-rw-r--r--cxmanage_api/tftp.py107
1 files changed, 44 insertions, 63 deletions
diff --git a/cxmanage_api/tftp.py b/cxmanage_api/tftp.py
index 02b7c49..e3aaec3 100644
--- a/cxmanage_api/tftp.py
+++ b/cxmanage_api/tftp.py
@@ -1,4 +1,7 @@
-# Copyright (c) 2012, Calxeda Inc.
+"""Calxeda: tftp.py"""
+
+
+# Copyright (c) 2012-2013, Calxeda Inc.
#
# All rights reserved.
#
@@ -29,22 +32,21 @@
# DAMAGE.
-import os
-import sys
-import atexit
import shutil
import socket
import logging
import traceback
+from datetime import datetime, timedelta
from tftpy import TftpClient, TftpServer, setLogLevel
from threading import Thread
from cxmanage_api import temp_dir
from tftpy.TftpShared import TftpException
-class InternalTftp(object):
- """Internally serves files using the `Trivial File Transfer Protocol <http://en.wikipedia.org/wiki/Trivial_File_Transfer_Protocol>`_.
+class InternalTftp(Thread):
+ """Internally serves files using the `Trivial File Transfer Protocol \
+<http://en.wikipedia.org/wiki/Trivial_File_Transfer_Protocol>`_.
>>> # Typical instantiation ...
>>> from cxmanage_api.tftp import InternalTftp
@@ -60,56 +62,45 @@ class InternalTftp(object):
:type verbose: boolean
"""
+ _default = None
+
+ @staticmethod
+ def default():
+ """ Return the default InternalTftp server """
+ if InternalTftp._default == None:
+ InternalTftp._default = InternalTftp()
+ return InternalTftp._default
def __init__(self, ip_address=None, port=0, verbose=False):
- """Default constructor for the InternalTftp class."""
+ super(InternalTftp, self).__init__()
+ self.daemon = True
+
self.tftp_dir = temp_dir()
self.verbose = verbose
- pipe = os.pipe()
- pid = os.fork()
- if (not pid):
- # Force tftpy to use sys.stdout and sys.stderr
- try:
- os.dup2(sys.stdout.fileno(), 1)
- os.dup2(sys.stderr.fileno(), 2)
-
- except AttributeError, err_msg:
- if (self.verbose):
- print ('Passing on exception: %s' % err_msg)
- pass
-
- # Create a PortThread class only if needed ...
- class PortThread(Thread):
- """Thread that sends the port number through the pipe."""
- def run(self):
- """Run function override."""
- # Need to wait for the server to open its socket
- while not server.sock:
- pass
- with os.fdopen(pipe[1], "w") as a_file:
- a_file.write("%i\n" % server.sock.getsockname()[1])
- #
- # Create an Internal TFTP server thread
- #
- server = TftpServer(tftproot=self.tftp_dir)
- thread = PortThread()
- thread.start()
- try:
- if not self.verbose:
- setLogLevel(logging.CRITICAL)
- # Start accepting connections ...
- server.listen(listenport=port)
- except KeyboardInterrupt:
- # User @ keyboard cancelled server ...
- if (self.verbose):
- traceback.format_exc()
- sys.exit(0)
-
- self.server = pid
+
+ self.server = TftpServer(tftproot=self.tftp_dir)
self.ip_address = ip_address
- with os.fdopen(pipe[0]) as a_fd:
- self.port = int(a_fd.readline())
- atexit.register(self.kill)
+ self.port = port
+ self.start()
+
+ # Get the port we actually hosted on
+ if port == 0:
+ deadline = datetime.now() + timedelta(seconds=10)
+ while datetime.now() < deadline:
+ try:
+ self.port = self.server.sock.getsockname()[1]
+ break
+ except (AttributeError, socket.error):
+ pass
+ else:
+ # don't catch the error on our last attempt
+ self.port = self.server.sock.getsockname()[1]
+
+ def run(self):
+ """ Run the server. Listens indefinitely. """
+ if not self.verbose:
+ setLogLevel(logging.CRITICAL)
+ self.server.listen(listenport=self.port)
def get_address(self, relative_host=None):
"""Returns the ipv4 address of this server.
@@ -136,16 +127,6 @@ class InternalTftp(object):
sock.close()
return ipv4
- def kill(self):
- """Kills the InternalTftpServer.
-
- >>> i_tftp.kill()
-
- """
- if (self.server):
- os.kill(self.server, 15)
- self.server = None
-
def get_file(self, src, dest):
"""Download a file from the tftp server to local_path.
@@ -153,7 +134,7 @@ class InternalTftp(object):
:param src: Source file path on the tftp_server.
:type src: string
- :param dest: Destination path (on your machine) to copy the TFTP file to.
+ :param dest: Destination path (local machine) to copy the TFTP file to.
:type dest: string
"""
@@ -222,7 +203,7 @@ class ExternalTftp(object):
>>> e_tftp.get_address()
'1.2.3.4'
- :param relative_host: Unused parameter present only for function signature.
+ :param relative_host: Unused parameter, for function signature.
:type relative_host: None
:returns: The ip address of the external TFTP server.