"""Calxeda: tftp.py""" # Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of Calxeda Inc. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. 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(Thread): """Internally serves files using the `Trivial File Transfer Protocol \ `_. >>> # Typical instantiation ... >>> from cxmanage_api.tftp import InternalTftp >>> i_tftp = InternalTftp() >>> # Alternatively, you can specify an address or hostname ... >>> i_tftp = InternalTftp(ip_address='localhost') :param ip_address: Ip address for the Internal TFTP server to use. :type ip_address: string :param port: Port for the internal TFTP server. :type port: integer :param verbose: Flag to turn on additional messaging. :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): super(InternalTftp, self).__init__() self.daemon = True self.tftp_dir = temp_dir() self.verbose = verbose self.server = TftpServer(tftproot=self.tftp_dir) self.ip_address = ip_address 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. If a relative_host is specified, then we discover our address to them. >>> i_tftp.get_address(relative_host='10.10.14.150') 'localhost' :param relative_host: Ip address to the relative host. :type relative_host: string :return: The ipv4 address of this InternalTftpServer. :rtype: string """ if (self.ip_address != None): return self.ip_address elif (relative_host == None): return "localhost" else: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.connect((relative_host, self.port)) ipv4 = sock.getsockname()[0] sock.close() return ipv4 def get_file(self, src, dest): """Download a file from the tftp server to local_path. >>> i_tftp.get_file(src='remote_file_i_want.txt', dest='/local/path') :param src: Source file path on the tftp_server. :type src: string :param dest: Destination path (local machine) to copy the TFTP file to. :type dest: string """ src = "%s/%s" % (self.tftp_dir, src) if (src != dest): try: # Ensure the file exists ... with open(src) as a_file: a_file.close() shutil.copy(src, dest) except Exception: traceback.format_exc() raise def put_file(self, src, dest): """Upload a file from src to dest on the tftp server (path). >>> i_tftp.put_file(src='/local/file.txt', dest='remote_file_name.txt') :param src: Path to the local file to send to the TFTP server. :type src: string :param dest: Path to put the file to on the TFTP Server. :type dest: string """ dest = "%s/%s" % (self.tftp_dir, dest) if (src != dest): try: # Ensure that the local file exists ... with open(src) as a_file: a_file.close() shutil.copy(src, dest) except Exception: traceback.format_exc() raise class ExternalTftp(object): """Defines a ExternalTftp object, which is actually TFTP client. >>> from cxmanage_api.tftp import ExternalTftp >>> e_tftp = ExternalTftp(ip_address='1.2.3.4') :param ip_address: Ip address of the TFTP server. :type ip_address: string :param port: Port to the External TFTP server. :type port: integer :param verbose: Flag to turn on verbose output (cmd/response). :type verbose: boolean """ def __init__(self, ip_address, port=69, verbose=False): """Default constructor for this the ExternalTftp class.""" self.ip_address = ip_address self.port = port self.verbose = verbose if not self.verbose: setLogLevel(logging.CRITICAL) def get_address(self, relative_host=None): """Return the ip address of the ExternalTftp server. >>> e_tftp.get_address() '1.2.3.4' :param relative_host: Unused parameter, for function signature. :type relative_host: None :returns: The ip address of the external TFTP server. :rtype: string """ del relative_host # Needed only for function signature. return self.ip_address def get_file(self, src, dest): """Download a file from the ExternalTftp Server. .. note:: * TftpClient is not threadsafe, so we create a unique instance for each transfer. >>> e_tftp.get_file(src='remote_file_i_want.txt', dest='/local/path') :param src: The path to the file on the Tftp server. :type src: string :param dest: The local destination to copy the file to. :type dest: string :raises TftpException: If the file does not exist or cannot be obtained from the TFTP server. :raises TftpException: If a TypeError is received from tftpy. """ try: client = TftpClient(self.ip_address, self.port) client.download(output=dest, filename=src) except TftpException: if (self.verbose): traceback.format_exc() raise except TypeError: if (self.verbose): traceback.format_exc() raise TftpException("Failed download file from TFTP server") def put_file(self, src, dest): """Uploads a file to the tftp server. .. note:: * TftpClient is not threadsafe, so we create a unique instance for each transfer. >>> e_tftp.put_file(src='local_file.txt', dest='remote_name.txt') :param src: Source file path (on your local machine). :type src: string :param dest: Destination path (on the TFTP server). :type dest: string :raises TftpException: If the file cannot be written to the TFTP server. :raises TftpException: If a TypeError is received from tftpy. """ try: client = TftpClient(self.ip_address, self.port) client.upload(input=src, filename=dest) except TftpException: if (self.verbose): traceback.format_exc() raise except TypeError: if (self.verbose): traceback.format_exc() raise TftpException("Failed to upload file to TFTP server") # End of file: ./tftp.py