// @(#)root/net:$Name:  $:$Id: TFTP.cxx,v 1.12 2002/03/20 18:54:56 rdm Exp $
// Author: Fons Rademakers   13/02/2001

/*************************************************************************
 * Copyright (C) 1995-2001, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

//////////////////////////////////////////////////////////////////////////
//                                                                      //
// TFTP                                                                 //
//                                                                      //
// This class provides all infrastructure for a performant file         //
// transfer protocol. It works in conjuction with the rootd daemon      //
// and can use parallel sockets to improve performance over fat pipes.  //
//                                                                      //
//////////////////////////////////////////////////////////////////////////

#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#ifndef WIN32
#   include <unistd.h>
#else
#   define ssize_t int
#   include <io.h>
#   include <sys/types.h>
#endif

#include "TFTP.h"
#include "TPSocket.h"
#include "TNetFile.h"
#include "TAuthenticate.h"
#include "TUrl.h"
#include "TStopwatch.h"
#include "TSystem.h"
#include "TEnv.h"
#include "TError.h"

#if defined(R__UNIX)
#define HAVE_MMAP
#endif

#ifdef HAVE_MMAP
#   include <unistd.h>
#   include <sys/mman.h>
#ifndef MAP_FILE
#define MAP_FILE 0           /* compatability flag */
#endif
#endif


Double_t TFTP::fgBytesWrite = 0;
Double_t TFTP::fgBytesRead  = 0;


ClassImp(TFTP)

//______________________________________________________________________________
 TFTP::TFTP(const char *url, Int_t par, Int_t wsize)
{
   // Open connection to host specified by the url using par parallel sockets.
   // The url has the form: [root[s]://]host[:port].
   // If port is not specified the default rootd port (1094) will be used.
   // Using wsize one can specify the tcp window size. Normally this is not
   // needed when using parallel sockets.

   fSocket = 0;

   TString s = url;
   if (s.Contains("://")) {
      if (!s.BeginsWith("root")) {
         Error("TFTP", "url must be of the form "[root[s]://]host[:port]"");
         MakeZombie();
         return;
      }
   } else
      s = "root://" + s;

   Init(s, par, wsize);
}

//______________________________________________________________________________
 void TFTP::Init(const char *surl, Int_t par, Int_t wsize)
{
   // Set up the actual connection.

   TAuthenticate *auth;
   EMessageTypes kind;
   Int_t sec;

   TUrl url(surl);

again:
   if (par > 1) {
      fSocket = new TPSocket(url.GetHost(), url.GetPort(), par, wsize);
      if (!fSocket->IsValid()) {
         Warning("TFTP", "can't open %d parallel connections to rootd on host %s at port %d",
                 par, url.GetHost(), url.GetPort());
         delete fSocket;
         par = 1;
         goto again;
      }

      // NoDelay is internally set by TPSocket

   } else {
      fSocket = new TSocket(url.GetHost(), url.GetPort(), wsize);
      if (!fSocket->IsValid()) {
         Error("TFTP", "can't open connection to rootd on host %s at port %d",
               url.GetHost(), url.GetPort());
         goto zombie;
      }

      // Set some socket options
      fSocket->SetOption(kNoDelay, 1);

      // Tell rootd we want non parallel connection
      fSocket->Send((Int_t) 0, (Int_t) 0);
   }

   // Get rootd protocol level
   fSocket->Send(kROOTD_PROTOCOL);
   Recv(fProtocol, kind);

   // Authenticate to remote rootd server
   sec = gEnv->GetValue("Rootd.Authentication", TAuthenticate::kClear);
   if (!strcmp(url.GetProtocol(), "roots"))
      sec = TAuthenticate::kSRP;
   if (!strcmp(url.GetProtocol(), "rootk"))
      sec = TAuthenticate::kKrb5;
   auth = new TAuthenticate(fSocket, url.GetHost(), "rootd", sec);
   if (!auth->Authenticate()) {
      if (sec == TAuthenticate::kSRP)
         Error("TFTP", "SRP authentication failed for host %s", url.GetHost());
      else if (sec == TAuthenticate::kKrb5)
         Error("TFTP", "Krb5 authentication failed for host %s", url.GetHost());
      else
         Error("TFTP", "authentication failed for host %s", url.GetHost());
      delete auth;
      goto zombie;
   }
   fUser = auth->GetUser();
   delete auth;

   fHost       = url.GetHost();
   fPort       = url.GetPort();
   fParallel   = par;
   fWindowSize = wsize;
   fLastBlock  = 0;
   fRestartAt  = 0;
   fBlockSize  = kDfltBlockSize;
   fMode       = kBinary;
   fBytesWrite = 0;
   fBytesRead  = 0;

   return;

zombie:
   MakeZombie();
   SafeDelete(fSocket);
}

//______________________________________________________________________________
 TFTP::~TFTP()
{
   // TFTP dtor. Send close message and close socket.

   Close();
   SafeDelete(fSocket);
}

//______________________________________________________________________________
 void TFTP::Print(Option_t *) const
{
   // Print some info about the FTP connection.

   Printf("Remote host:          %s [%d]", fHost.Data(), fPort);
   Printf("Remote user:          %s", fUser.Data());
   if (fParallel > 1)
      Printf("Parallel sockets:     %d", fParallel);
   Printf("TCP window size:      %d", fWindowSize);
   Printf("Rootd protocol:       %d", fProtocol);
   Printf("Transfer block size:  %d", fBlockSize);
   Printf("Transfer mode:        %s", fMode ? "ascii" : "binary");
   Printf("Bytes sent:           %g", fBytesWrite);
   Printf("Bytes received:       %g", fBytesRead);
}

//______________________________________________________________________________
 void TFTP::PrintError(const char *where, Int_t err) const
{
   // Print error string depending on error code.

   Error(where, gRootdErrStr[err]);
}

//______________________________________________________________________________
 Int_t TFTP::Recv(Int_t &status, EMessageTypes &kind) const
{
   // Return status from rootd server and message kind. Returns -1 in
   // case of error otherwise 8 (sizeof 2 words, status and kind).

   kind   = kROOTD_ERR;
   status = 0;

   if (!fSocket) return -1;

   Int_t what;
   Int_t n = fSocket->Recv(status, what);
   kind = (EMessageTypes) what;
   return n;
}

//______________________________________________________________________________
 void TFTP::SetBlockSize(Int_t blockSize)
{
   // Make sure the block size is a power of two, with a minimum of 32768.

   if (blockSize < 32768) {
      fBlockSize = 32768;
      return;
   }

   int i;
   for (i = 0; i < int(sizeof(blockSize)*8); i++)
      if ((blockSize >> i) == 1)
         break;

   fBlockSize = 1 << i;
}

//______________________________________________________________________________
 Seek_t TFTP::PutFile(const char *file, const char *remoteName)
{
   // Transfer file to remote host. Returns number of bytes
   // sent or < 0 in case of error. Error -1 connection is still
   // open, error -2 connection has been closed. In case of failure
   // fRestartAt is set to the number of bytes correclty transfered.
   // Calling PutFile() immediately afterwards will restart at fRestartAt.
   // If this is not desired call SetRestartAt(0) before calling PutFile().
   // If rootd reports that the file is locked, and you are sure this is not
   // the case (e.g. due to a crash), you can force unlock it by prepending
   // the remoteName with a '-'.

   if (!IsOpen() || !file || !*file) return -1;

#ifndef WIN32
   Int_t fd = open(file, O_RDONLY);
#else
   Int_t fd = open(file, O_RDONLY | O_BINARY);
#endif
   if (fd < 0) {
      Error("PutFile", "cannot open %s in read mode", file);
      return -1;
   }

   Long_t id, size, flags, modtime;
   if (gSystem->GetPathInfo(file, &id, &size, &flags, &modtime) == 0) {
      if (flags > 1) {
         Error("PutFile", "%s not a regular file (%ld)", file, flags);
         close(fd);
         return -1;
      }
   } else {
      Warning("PutFile", "could not stat %s", file);
      close(fd);
      return -1;
   }

   if (!remoteName)
      remoteName = file;

   Long_t restartat = fRestartAt;

   // check if restartat value makes sense
   if (restartat && (restartat >= size))
      restartat = 0;

   if (fSocket->Send(Form("%s %d %d %ld %ld", remoteName, fBlockSize, fMode,
                     size, restartat), kROOTD_PUTFILE) < 0) {
      Error("PutFile", "error sending kROOTD_PUTFILE command");
      close(fd);
      return -2;
   }

   Int_t         stat;
   EMessageTypes kind;

   if (Recv(stat, kind) < 0 || kind == kROOTD_ERR) {
      PrintError("PutFile", stat);
      close(fd);
      return -1;
   }

   Printf("<TFTP::PutFile>: sending file %s (%ld bytes, starting at %ld)",
          file, size, restartat);

   TStopwatch timer;
   timer.Start();

   Seek_t pos = restartat & ~(fBlockSize-1);
   Int_t skip = restartat - pos;

#ifndef HAVE_MMAP
   char *buf = new char[fBlockSize];
   lseek(fd, pos, SEEK_SET);
#endif

   while (pos < size) {
      Seek_t left = Seek_t(size - pos);
      if (left > fBlockSize)
         left = fBlockSize;
#ifdef HAVE_MMAP
      char *buf = (char*) mmap(0, left, PROT_READ, MAP_FILE | MAP_SHARED, fd, pos);
      if (buf == (char *) -1) {
         Error("PutFile", "mmap of file %s failed", file);
         close(fd);
         return -1;
      }
#else
      Int_t siz;
      while ((siz = read(fd, buf, left)) < 0 && TSystem::GetErrno() == EINTR)
         TSystem::ResetErrno();
      if (siz < 0 || siz != left) {
         Error("PutFile", "error reading from file %s", file);
         // Send urgent message to rootd to stop tranfer
         delete [] buf;
         close(fd);
         return -1;
      }
#endif

      if (fSocket->SendRaw(buf+skip, left-skip) < 0) {
         Error("PutFile", "error sending buffer");
         // Send urgent message to rootd to stop transfer
#ifdef HAVE_MMAP
         munmap(buf, left);
#else
         delete [] buf;
#endif
         close(fd);
         return -2;
      }

      fBytesWrite  += left-skip;
      fgBytesWrite += left-skip;

      fRestartAt = pos;   // bytes correctly sent up till now

      pos += left;
      skip = 0;

#ifdef HAVE_MMAP
      munmap(buf, left);
#endif
   }

#ifndef HAVE_MMAP
   delete [] buf;
#endif

   close(fd);

   fRestartAt = 0;

   // get acknowlegdement from server that file was stored correctly
   if (Recv(stat, kind) < 0 || kind == kROOTD_ERR) {
      PrintError("PutFile", stat);
      close(fd);
      return -1;
   }

   // provide timing numbers
   Double_t speed, t = timer.RealTime();
   if (t > 0)
      speed = (size - restartat) / t;
   else
      speed = 0.0;
   if (speed > 524288)
      Printf("<TFTP::PutFile>: %.3f seconds, %.2f Mbytes per second",
             t, speed / 1048576);
   else if (speed > 512)
      Printf("<TFTP::PutFile>: %.3f seconds, %.2f Kbytes per second",
             t, speed / 1024);
   else
      Printf("<TFTP::PutFile>: %.3f seconds, %.2f bytes per second",
             t, speed);

   return Seek_t(size - restartat);
}

//______________________________________________________________________________
 Seek_t TFTP::GetFile(const char *file, const char *localName)
{
   // Transfer file from remote host. Returns number of bytes
   // received or < 0 in case of error. Error -1 connection is still
   // open, error -2 connection has been closed. In case of failure
   // fRestartAt is set to the number of bytes correclty transfered.
   // Calling GetFile() immediately afterwards will restart at fRestartAt.
   // If this is not desired call SetRestartAt(0) before calling GetFile().
   // If rootd reports that the file is locked, and you are sure this is not
   // the case (e.g. due to a crash), you can force unlock it by prepending
   // the file name with a '-'.

   if (!IsOpen() || !file || !*file) return -1;

   if (!localName) {
      if (file[0] == '-')
         localName = file+1;
      else
         localName = file;
   }

   Long_t restartat = fRestartAt;

   if (fSocket->Send(Form("%s %d %d %ld", file, fBlockSize, fMode,
                     restartat), kROOTD_GETFILE) < 0) {
      Error("GetFile", "error sending kROOTD_GETFILE command");
      return -2;
   }

   Int_t         stat;
   EMessageTypes kind;

   if (Recv(stat, kind) < 0 || kind == kROOTD_ERR) {
      PrintError("GetFile", stat);
      return -1;
   }

   // get size of remote file
   Long_t sizel;
   Int_t  what;
   char   mess[64];

   if (fSocket->Recv(mess, sizeof(mess), what) < 0) {
      Error("GetFile", "error receiving remote file size");
      return -2;
   }
   sscanf(mess, "%ld", &sizel);
   Seek_t size = (Seek_t) sizel;

   // check if restartat value makes sense
   if (restartat && (restartat >= size))
      restartat = 0;

   // open local file
   Int_t fd;
   if (!restartat) {
#ifndef WIN32
      fd = open(localName, O_CREAT | O_TRUNC | O_WRONLY, 0600);
#else
      if (fMode == kBinary)
         fd = open(localName, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY,
                   S_IREAD | S_IWRITE);
      else
         fd = open(localName, O_CREAT | O_TRUNC | O_WRONLY,
                   S_IREAD | S_IWRITE);
#endif
   } else {
#ifndef WIN32
      fd = open(localName, O_WRONLY, 0600);
#else
      if (fMode == kBinary)
         fd = open(localName, O_WRONLY | O_BINARY, S_IREAD | S_IWRITE);
      else
         fd = open(localName, O_WRONLY, S_IREAD | S_IWRITE);
#endif
   }

   if (fd < 0) {
      Error("GetFile", "cannot open %s", localName);
      // send urgent message to rootd to stop tranfer
      return -1;
   }

   // check file system space
   if (strcmp(localName, "/dev/null")) {
      Long_t id, bsize, blocks, bfree;
      if (gSystem->GetFsInfo(localName, &id, &bsize, &blocks, &bfree) == 0) {
         Double_t space = (Double_t)bsize * (Double_t)bfree;
         if (space < size - restartat) {
            Error("GetFile", "not enough space to store file %s", localName);
            // send urgent message to rootd to stop tranfer
            close(fd);
            return -1;
         }
      } else
         Warning("GetFile", "could not determine if there is enough free space to store file");
   }

   // seek to restartat position
   if (restartat) {
      if (lseek(fd, (off_t) restartat, SEEK_SET) < 0) {
         Error("GetFile", "cannot seek to position %ld in file %s",
               restartat, localName);
         // if cannot seek send urgent message to rootd to stop tranfer
         close(fd);
         return -1;
      }
   }

   Printf("<TFTP::GetFile>: getting file %s (%ld bytes, starting at %ld)",
          localName, sizel, restartat);

   TStopwatch timer;
   timer.Start();

   char *buf = new char[fBlockSize];
   char *buf2 = 0;
   if (fMode == kAscii)
      buf2 = new char[fBlockSize];

   Seek_t pos = restartat & ~(fBlockSize-1);
   Int_t skip = restartat - pos;

   while (pos < size) {
      Seek_t left = size - pos;
      if (left > fBlockSize)
         left = fBlockSize;

      Int_t n;
      while ((n = fSocket->RecvRaw(buf, Int_t(left-skip))) < 0 &&
             TSystem::GetErrno() == EINTR)
         TSystem::ResetErrno();

      if (n != Int_t(left-skip)) {
         Error("GetFile", "error receiving buffer of length %d, got %d",
               Int_t(left-skip), n);
         close(fd);
         delete [] buf; delete [] buf2;
         return -2;
      }

      // in case of ascii file, loop over buffer and remove r's
      ssize_t siz;
      if (fMode == kAscii) {
         Int_t i = 0, j = 0;
         while (i < n) {
            if (buf[i] == 'r')
               i++;
            else
               buf2[j++] = buf[i++];
         }
         n = j;
         while ((siz = write(fd, buf2, n)) < 0 && TSystem::GetErrno() == EINTR)
            TSystem::ResetErrno();
      } else {
         while ((siz = write(fd, buf, n)) < 0 && TSystem::GetErrno() == EINTR)
            TSystem::ResetErrno();
      }

      if (siz < 0) {
         SysError("GetFile", "error writing file %s", localName);
         // send urgent message to rootd to stop tranfer
         close(fd);
         delete [] buf; delete [] buf2;
         return -1;
      }

      if (siz != n) {
         Error("GetFile", "error writing all requested bytes to file %s, wrote %d of %d",
               localName, siz, n);
         // send urgent message to rootd to stop tranfer
         close(fd);
         delete [] buf; delete [] buf2;
         return -1;
      }

      fBytesRead  += Int_t(left-skip);
      fgBytesRead += Int_t(left-skip);

      fRestartAt = pos;   // bytes correctly received up till now

      pos += left;
      skip = 0;
   }

   delete [] buf; delete [] buf2;

#ifndef WIN32
   fchmod(fd, 0644);
#endif

   close(fd);

   fRestartAt = 0;

   // provide timing numbers
   Double_t speed, t = timer.RealTime();
   if (t > 0)
      speed = (size - restartat) / t;
   else
      speed = 0.0;
   if (speed > 524288)
      Printf("<TFTP::GetFile>: %.3f seconds, %.2f Mbytes per second",
             t, speed / 1048576);
   else if (speed > 512)
      Printf("<TFTP::GetFile>: %.3f seconds, %.2f Kbytes per second",
             t, speed / 1024);
   else
      Printf("<TFTP::GetFile>: %.3f seconds, %.2f bytes per second",
             t, speed);

   return Seek_t(size - restartat);
}

//______________________________________________________________________________
 Int_t TFTP::ChangeDirectory(const char *dir) const
{
   // Change the remote directory. If the remote directory contains a .message
   // file and it is < 1024 characters then the contents is echoed back.
   // Returns 0 in case of success and -1 in case of failure.

   if (!IsOpen()) return -1;

   if (!dir || !*dir) {
      Error("ChangeDirectory", "illegal directory name specified");
      return -1;
   }

   if (fSocket->Send(Form("%s", dir), kROOTD_CHDIR) < 0) {
      Error("ChangeDirectory", "error sending kROOTD_CHDIR command");
      return -1;
   }

   Int_t what;
   char  mess[1024];

   if (fSocket->Recv(mess, sizeof(mess), what) < 0) {
      Error("ChangeDirectory", "error receiving chdir confirmation");
      return -1;
   }
   if (what == kMESS_STRING) {
      Printf("%sn", mess);

      if (fSocket->Recv(mess, sizeof(mess), what) < 0) {
         Error("ChangeDirectory", "error receiving chdir confirmation");
         return -1;
      }
   }

   Printf("<TFTP::ChangeDirectory>: %s", mess);

   return 0;
}

//______________________________________________________________________________
 Int_t TFTP::MakeDirectory(const char *dir) const
{
   // Make a remote directory. Anonymous users may not create directories.
   // Returns 0 in case of success and -1 in case of failure.

   if (!IsOpen()) return -1;

   if (!dir || !*dir) {
      Error("MakeDirectory", "illegal directory name specified");
      return -1;
   }

   if (fSocket->Send(Form("%s", dir), kROOTD_MKDIR) < 0) {
      Error("MakeDirectory", "error sending kROOTD_MKDIR command");
      return -1;
   }

   Int_t what;
   char  mess[1024];;

   if (fSocket->Recv(mess, sizeof(mess), what) < 0) {
      Error("MakeDirectory", "error receiving mkdir confirmation");
      return -1;
   }

   Printf("<TFTP::MakeDirectory>: %s", mess);

   return 0;
}

//______________________________________________________________________________
 Int_t TFTP::DeleteDirectory(const char *dir) const
{
   // Delete a remote directory. Anonymous users may not delete directories.
   // Returns 0 in case of success and -1 in case of failure.

   if (!IsOpen()) return -1;

   if (!dir || !*dir) {
      Error("DeleteDirectory", "illegal directory name specified");
      return -1;
   }

   if (fSocket->Send(Form("%s", dir), kROOTD_RMDIR) < 0) {
      Error("DeleteDirectory", "error sending kROOTD_RMDIR command");
      return -1;
   }

   Int_t what;
   char  mess[1024];;

   if (fSocket->Recv(mess, sizeof(mess), what) < 0) {
      Error("DeleteDirectory", "error receiving rmdir confirmation");
      return -1;
   }

   Printf("<TFTP::DeleteDirectory>: %s", mess);

   return 0;
}

//______________________________________________________________________________
 Int_t TFTP::ListDirectory(Option_t *cmd) const
{
   // List remote directory. With cmd you specify the options and directory
   // to be listed to ls. Returns 0 in case of success and -1 in case of
   // failure.

   if (!IsOpen()) return -1;

   if (!cmd || !*cmd)
      cmd = "ls .";

   if (fSocket->Send(Form("%s", cmd), kROOTD_LSDIR) < 0) {
      Error("ListDirectory", "error sending kROOTD_LSDIR command");
      return -1;
   }

   Int_t what;
   char  mess[1024];;

   do {
      if (fSocket->Recv(mess, sizeof(mess), what) < 0) {
         Error("ListDirectory", "error receiving lsdir confirmation");
         return -1;
      }
      printf("%s", mess);
   } while (what == kMESS_STRING);

   return 0;
}

//______________________________________________________________________________
 Int_t TFTP::PrintDirectory() const
{
   // Print path of remote working directory. Returns 0 in case of succes and
   // -1 in cse of failure.

   if (!IsOpen()) return -1;

   if (fSocket->Send("", kROOTD_PWD) < 0) {
      Error("DeleteDirectory", "error sending kROOTD_PWD command");
      return -1;
   }

   Int_t what;
   char  mess[1024];;

   if (fSocket->Recv(mess, sizeof(mess), what) < 0) {
      Error("PrintDirectory", "error receiving pwd confirmation");
      return -1;
   }

   Printf("<TFTP::PrintDirectory>: %s", mess);

   return 0;
}

//______________________________________________________________________________
 Int_t TFTP::RenameFile(const char *file1, const char *file2) const
{
   // Rename a remote file. Anonymous users may not rename files.
   // Returns 0 in case of success and -1 in case of failure.

   if (!IsOpen()) return -1;

   if (!file1 || !file2 || !*file1 || !*file2) {
      Error("RenameFile", "illegal file names specified");
      return -1;
   }

   if (fSocket->Send(Form("%s %s", file1, file2), kROOTD_MV) < 0) {
      Error("RenameFile", "error sending kROOTD_MV command");
      return -1;
   }

   Int_t what;
   char  mess[1024];;

   if (fSocket->Recv(mess, sizeof(mess), what) < 0) {
      Error("RenameFile", "error receiving mv confirmation");
      return -1;
   }

   Printf("<TFTP::RenameFile>: %s", mess);

   return 0;
}

//______________________________________________________________________________
 Int_t TFTP::DeleteFile(const char *file) const
{
   // Delete a remote file. Anonymous users may not delete files.
   // Returns 0 in case of success and -1 in case of failure.

   if (!IsOpen()) return -1;

   if (!file || !*file) {
      Error("DeleteFile", "illegal file name specified");
      return -1;
   }

   if (fSocket->Send(Form("%s", file), kROOTD_RM) < 0) {
      Error("DeleteFile", "error sending kROOTD_RM command");
      return -1;
   }

   Int_t what;
   char  mess[1024];;

   if (fSocket->Recv(mess, sizeof(mess), what) < 0) {
      Error("DeleteFile", "error receiving rm confirmation");
      return -1;
   }

   Printf("<TFTP::DeleteFile>: %s", mess);

   return 0;
}

//______________________________________________________________________________
 Int_t TFTP::ChangePermission(const char *file, Int_t mode) const
{
   // Change permissions of a remote file. Anonymous users may not
   // chnage permissions. Returns 0 in case of success and -1 in case
   // of failure.

   if (!IsOpen()) return -1;

   if (!file || !*file) {
      Error("ChangePermission", "illegal file name specified");
      return -1;
   }

   if (fSocket->Send(Form("%s %d", file, mode), kROOTD_CHMOD) < 0) {
      Error("ChangePermission", "error sending kROOTD_CHMOD command");
      return -1;
   }

   Int_t what;
   char  mess[1024];;

   if (fSocket->Recv(mess, sizeof(mess), what) < 0) {
      Error("ChangePermission", "error receiving chmod confirmation");
      return -1;
   }

   Printf("<TFTP::ChangePermission>: %s", mess);

   return 0;
}

//______________________________________________________________________________
 Int_t TFTP::Close()
{
   // Close ftp connection. Returns 0 in case of success and -1 in case of
   // failure.

   if (!IsOpen()) return -1;

   if (fSocket->Send(kROOTD_CLOSE) < 0) {
      Error("Close", "error sending kROOTD_CLOSE command");
      return -1;
   }

   return 0;
}


ROOT page - Class index - Top of the page

This page has been automatically generated. If you have any comments or suggestions about the page layout send a mail to ROOT support, or contact the developers with any questions or problems regarding ROOT.