/*-------------------------------------------------------------------------- Copyright 1999, 2000 by Dan Kegel http://www.kegel.com/ See the file COPYING This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------- Module to handle the networking calls for the client side of the FTP protocol. Delegates the work of parsing and generating messages to the ftp_client_protocol module. --------------------------------------------------------------------------*/ #include "dprint.h" #include "ftp_client_pipe.h" #include /* for AF_INET */ #include #include #include #include #include #include #include #include #include /* Or this flag into the 'events' argument of handle_io() * to indicate a kickstart call from inside a wrapper function */ #define KICKSTART 0x8000 /* return whether the given status code indicates a given FTP command status */ #define STATUS_OK(s) (((s) >= 200) && ((s) <= 299)) #define GOTO_STATE(fn, s) do { DPRINT((fn ":goto_state(%d): old state %d, cfd %d; line %d\n", s, m_state, m_cfd, __LINE__)); m_state = s; } while (0) /*---------------------------------------------------------------------- Portable function to set a socket into nonblocking mode. ----------------------------------------------------------------------*/ static int setNonblocking(int fd) { int flags; /* Caution: O_NONBLOCK is defined but broken on SunOS 4.1.x and AIX 3.2.5. */ #if defined(O_NONBLOCK) if (-1 == (flags = fcntl(fd, F_GETFL, 0))) flags = 0; return fcntl(fd, F_SETFL, flags | O_NONBLOCK); #else flags = 1; return ioctl(fd, FIOBIO, &flags); #endif } /*---------------------------------------------------------------------- Initialize this object, and mark it as not connected. The calling object should implement the ftp_client_pipe_datainterface_t interface and pass a pointer to itself. The remining arguments are a pointer to the shared global scheduler, and the maximum bytes per second to allow this client. After each call to handle_data_read(), this module will use the scheduler to sleep long enough to stay under the specified bandwidth. @param local_addr if not 0, local_addr specifies the local IP address to use for the local side of the connection. Must be stable pointer. ----------------------------------------------------------------------*/ void ftp_client_pipe_t::init(ftp_client_pipe_datainterface_t * datainterface, Sked *sked, int max_bytes_per_sec, Poller *poller, struct sockaddr_in *local_addr) { m_local_addr = local_addr; m_poller = poller; m_proto.init(); m_ibuf.init(); m_obuf.init(); m_in_ftpCmdDone = 0; m_cfd = -1; m_dfd = -1; m_iline[0] = 0; m_cfd_connecting = m_dfd_connecting = false; m_datainterface = datainterface; m_sked = sked; m_bytesPerSec = max_bytes_per_sec; m_bytesPerTick = max_bytes_per_sec / eclock_hertz(); //if (m_bytesPerTick < 1) { //printf("warning: max_bytes_per_sec %d < eclock_hertz() %d; clamping to 1 byte per tick\n", //max_bytes_per_sec, eclock_hertz()); //m_bytesPerTick = 1; //} m_dfd_events = 0; GOTO_STATE("init", IDLE); DPRINT(("ftp_client_pipe_t::init(%p, %p, %d): bytes_per_tick %d\n", datainterface, sked, max_bytes_per_sec, m_bytesPerTick)); } /* Do a connect with the given local address, and pick an ephemeral port by hand. Yuck! @param dest where to connect to @param src NULL, or IP address to connect *from*. Note: src->port is updated! */ static int ephemeral_connect(int sock, struct sockaddr_in *dest, struct sockaddr_in *src) { const int minport = 1024; const int maxport = 51023; DPRINT(("ephemeral_connect(fd %d, dest %p, src %p)\n", sock, dest, src)); if (!src) return ::connect(sock, (struct sockaddr *) dest, sizeof(*dest)); int remaining = maxport - minport; while (remaining--) { /* Increment the ephemeral address for that address */ unsigned short port = ntohs(src->sin_port) + 1; if ((port < minport) || (port > maxport)) port = minport; src->sin_port = htons(port); DPRINT(("ephemeral_connect: trying port %d\n", port)); if (bind(sock, (struct sockaddr *)src, sizeof(*src))) { if (errno != EADDRINUSE) { DPRINT(("ephemeral_connect: bind failed, errno %d\n", errno)); return -1; } } else break; } return ::connect(sock, (struct sockaddr *)dest, sizeof(*dest)); } /*---------------------------------------------------------------------- Initialize this object and start a connection to the given server. ----------------------------------------------------------------------*/ int ftp_client_pipe_t::connect(const char *hostname, int port) { DPRINT(("ftp_client_pipe_t::connect(%s, %d)\n", hostname, port)); /* reinit all fields but m_datainterface */ init(m_datainterface, m_sked, m_bytesPerSec, m_poller, m_local_addr); struct sockaddr_in address; address.sin_family = AF_INET; address.sin_port = htons(port); /* If the argument is a numerical IP address, parse it directly; * else try to look it up in DNS. */ if (!inet_aton(hostname, &address.sin_addr)) { struct hostent * host; host = gethostbyname(hostname); if (!host) { /* We can't find an IP number */ int err; switch (h_errno) { case HOST_NOT_FOUND: err = ENOENT; DPRINT(("gethostby*: HOST_NOT_FOUND\n")); break; case NO_DATA: err = ENOENT; DPRINT(("gethostby*: NO_DATA\n")); break; case NO_RECOVERY: err = ENOENT; DPRINT(("gethostby*: NO_RECOVERY\n")); break; case TRY_AGAIN: err = ENOENT; DPRINT(("gethostby*: TRY_AGAIN\n")); break; default: err = ENOENT; DPRINT(("gethostby*: h_errno %d???\n", h_errno)); break; } DPRINT(("error looking up host, returning %d\n", err)); return err; } /* Take the first IP address associated with this hostname */ memcpy(&address.sin_addr, host->h_addr_list[0], sizeof(address.sin_addr)); } int sock; int err; if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { err = errno; DPRINT(("ftp_client_pipe_t::connect: socket failed, returning %d\n", err)); return err; } if (setNonblocking(sock) == -1) { err = errno; DPRINT(("ftp_client_pipe_t::init: setNonblocking failed, returning %d\n", err)); close(sock); return err; } if (ephemeral_connect(sock, &address, m_local_addr)) { err = errno; if (errno != EINPROGRESS) { DPRINT(("ftp_client_pipe_t::init: Connect failed, returning %d\n", err)); close(sock); return err; } m_cfd_connecting = true; DPRINT(("ftp_client_pipe_t::init: Waiting for connect to finish, m_cfd %d\n", sock)); } else { DPRINT(("ftp_client_pipe_t::init: Connect succeded early, m_cfd %d\n", sock)); m_cfd_connecting = false; } m_cfd = sock; err = m_poller->add(m_cfd, this, POLLIN|POLLOUT); if (err) { EDPRINT(("ftp_client_pipe_t::init: add failed\n")); return err; } m_iline_full = false; /* Abort if no response in five seconds */ m_sked->addClient(this, eclock()+ (5 * eclock_hertz())); return 0; } /*---------------------------------------------------------------------- Call this when done with the session. Closes the file descriptors. Returns 0 on success, else unix error code. ----------------------------------------------------------------------*/ int ftp_client_pipe_t::shutdown() { int err1 = 0, err2 = 0; int cfd, dfd; EDPRINT(("ftp_client_pipe_t::shutdown(): m_state %d, cfd %d, dfd %d, id %d\n", m_state, m_cfd, m_dfd, m_datainterface->getID())); if ((m_cfd == -1) && (m_dfd == -1)) return 0; /* don't do anything if already shut down */ m_sked->delClient(this); /* just in case some event is pending */ GOTO_STATE("shutdown", IDLE); m_cfd_connecting = false; m_dfd_connecting = false; dfd = m_dfd; cfd = m_cfd; m_dfd = -1; m_cfd = -1; if (cfd != -1) { m_poller->del(cfd); err1 = close(cfd); } if (dfd != -1) { m_poller->del(dfd); err2 = close(dfd); } return err1 ? err1 : err2; } /** Call m_datainterface->ftpCmdDone() if not already in it; avoids re-entry. Ah, the joys of immediate callbacks. */ void ftp_client_pipe_t::call_ftpCmdDone(int xerr, int status, const char *statusbuf) { if (m_in_ftpCmdDone == 0) { m_in_ftpCmdDone++; m_datainterface->ftpCmdDone(xerr, status, statusbuf); m_in_ftpCmdDone--; } } /// Handle events on the data file descriptor. Returns EALREADY if we might be done. int ftp_client_pipe_t::notifyPollEvent_dfd(Poller::PollEvent *event) { int xerr = 0; /* error to call ftpClientDone() with */ int fd = event->fd; int revents = event->revents; clock_t now = eclock(); int nxfer = 0; bool hup = false; assert(m_state != IDLE); if (m_state == IDLE) return 0; if (m_dfd_connecting && ((revents & KICKSTART|POLLOUT) == POLLOUT)) { /* check to see if connect succeeded */ int connecterr = -1; socklen_t len = sizeof(connecterr); if (getsockopt(m_dfd, SOL_SOCKET, SO_ERROR, (char *)&connecterr, &len) < 0) { xerr = errno; EDPRINT(("ftp_client_pipe_t::notifyPollEvent_dfd: getsockopt dfd %d fails, errno %d\n", m_dfd, xerr)); /* Caller will call stop() */ return xerr; } if (connecterr == EINPROGRESS) { EDPRINT(("ftp_client_pipe_t::notifyPollEvent_dfd: connect dfd %d EINPROGRESS\n", m_dfd)); m_poller->clearReadiness(m_dfd, POLLOUT); } else if (connecterr) { EDPRINT(("ftp_client_pipe_t::notifyPollEvent_dfd: connect dfd %d failed, errno %d\n", m_dfd, connecterr)); /* Caller will call stop() */ return connecterr; } else { m_dfd_connecting = false; DPRINT(("ftp_client_pipe_t::notifyPollEvent_dfd: Connect dfd %d succeeded\n", m_dfd)); } } /* If the OS is ready before we are, sleep */ /* Skip this if KICKSTART, because we don't want to sleep at first */ /* Skip this if POLLHUP, because the socket is about to close, no point sleeping */ if ((m_state == GETTING) && !(revents & (KICKSTART|POLLHUP)) && eclock_after(m_wakeup, now)) { DPRINT(("ftp_client_pipe_t::notifyPollEvent_dfd: dfd %d still needs to snooze for %d ticks\n", m_dfd, m_wakeup - now)); assert(skedIndex == 0); /* we shouldn't be scheduled yet */ m_sked->addClient(this, m_wakeup); GOTO_STATE("notifyPollEvent_dfd", SLEEPING); } /* If sleeping, and it's just a read event, just remember it */ if (m_state == SLEEPING) { m_dfd_events |= revents; DPRINT(("ftp_client_pipe_t::notifyPollEvent_dfd: dfd %d asleep; revents %x, m_dfd_events %x\n", m_dfd, revents, m_dfd_events)); } else if (((m_state == GETTING) && (revents & POLLIN)) || ((m_state == PUTTING) && (revents & POLLOUT))) { /* cancel timeout */ m_sked->delClient(this); /* If appropriate, transfer a packet */ DPRINT(("ftp_client_pipe_t::notifyPollEvent_dfd: calling subclass\n")); nxfer = m_datainterface->handle_data_io(fd, revents, now); if (nxfer == 0) hup = true; if (nxfer == -EWOULDBLOCK) { // You must tell poller about EWOULDBLOCK -- it has no other way to // know that a socket is no longer ready for I/O! m_poller->clearReadiness(m_dfd, (m_state == GETTING) ? POLLIN : POLLOUT); nxfer = 0; } if (nxfer < 0) { EDPRINT(("ftp_client_pipe_t::notifyPollEvent_dfd: subclass returns error %d! Returning EPIPE.\n", -nxfer)); /* Caller will call stop() */ return EPIPE; } } else if (revents & POLLHUP) { hup = true; } /* If handle is done (either via POLLHUP or nxfer = 0), close it */ if (hup) { GOTO_STATE("notifyPollEvent_dfd", IDLE); DPRINT(("ftp_client_pipe_t::notifyPollEvent_dfd: closing dfd %d\n", m_dfd)); m_sked->delClient(this); /* just in case some event is pending */ int dfd = m_dfd; m_dfd = -1; m_poller->del(dfd); close(dfd); /* If a data connection is finished, so are we, and * we might need to notify app that the command is done. */ return EALREADY; } if (m_state == GETTING) { /* Normal read. Throttle. */ nxfer += m_bytesUnsleptFor; clock_t howlong; static int hertz = eclock_hertz(); if (m_bytesPerTick < 5) { howlong = (nxfer * hertz) / m_bytesPerSec; m_bytesUnsleptFor = nxfer - (howlong * m_bytesPerSec) / hertz; } else { howlong = nxfer / m_bytesPerTick; m_bytesUnsleptFor = nxfer % m_bytesPerTick; } m_wakeup = now + howlong; DPRINT(("ftp_client_pipe_t::notifyPollEvent_dfd: dfd %d read %d bytes, won't read again for %d ticks\n", m_dfd, nxfer, howlong)); m_dfd_events = 0; } return 0; } /// Handle events on the control file descriptor. Returns EALREADY if we might be done. int ftp_client_pipe_t::notifyPollEvent_cfd(Poller::PollEvent *event) { int xerr = 0; /* error to call ftpClientDone() with */ int status = 0; int fd = event->fd; int revents = event->revents; DPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd(fd %d, %x): state %d, cning %d\n", fd, revents, m_state, m_cfd_connecting)); assert(fd != -1); if (revents & (POLLERR | POLLHUP)) { EDPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: poll indicates fd %d screwed up, revents %x\n", m_cfd, revents)); return EPIPE; } if (m_cfd_connecting && ((revents & (POLLOUT|KICKSTART)) == POLLOUT)) { /* check to see if connect succeeded */ int connecterr = -1; socklen_t len = sizeof(connecterr); if (getsockopt(m_cfd, SOL_SOCKET, SO_ERROR, (char *)&connecterr, &len) < 0) { int err = errno; EDPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: getsockopt cfd %d fails, errno %d\n", m_cfd, err)); return err; } if (connecterr == EINPROGRESS) { EDPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: connect cfd %d EINPROGRESS\n", m_cfd)); m_poller->clearReadiness(m_cfd, POLLOUT); } else if (connecterr) { EDPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: cfd %d connect failed, errno %d\n", m_cfd, connecterr)); /* Caller will call stop() */ return connecterr; } else { m_cfd_connecting = false; DPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: Connect cfd %d succeeded\n", m_cfd)); } } if (revents & POLLIN) { /* Read it */ int err = m_ibuf.fillFrom(m_cfd); if (err && (err != EWOULDBLOCK)) { EDPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: fillFrom returns %d?\n", err)); return err; } // You must tell poller about EWOULDBLOCK -- it has no other way to // know that a socket is no longer ready for I/O! if (err == EWOULDBLOCK) m_poller->clearReadiness(m_cfd, POLLIN); } //DPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd(fd %d, %d): before loop: m_proto.isInputReady() %d, m_proto.isOutputReady() %d\n", fd, (int) revents, m_proto.isInputReady(), m_proto.isOutputReady())); /* Process all lines in m_ibuf */ int puterr = 0; int geterr = 0; while ((m_proto.isInputReady() && (geterr != EWOULDBLOCK)) || (m_proto.isOutputReady() && (puterr != EWOULDBLOCK))) { //DPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd(%d): m_proto.isInputReady() %d, geterr %d, m_proto.isOutputReady() %d, puterr %d\n", revents, m_proto.isInputReady(), geterr, m_proto.isOutputReady(), puterr)); if (!m_iline_full) { geterr = m_ibuf.readline(m_iline, sizeof(m_iline)); if (!geterr) { m_iline_full = true; DPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd fd %d got '%s'\n", m_cfd, m_iline)); } else if (geterr != EWOULDBLOCK) return geterr; } if (m_iline_full) { if (m_state == IDLE) { /* cancel timeout*/ m_sked->delClient(this); } /* A line is ready. Process it. */ int err = m_proto.giveInput(m_iline); if (!err) { m_iline_full = false; } else if (err != EWOULDBLOCK) { EDPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: m_proto.giveInput returns %d\n",err)); return err; } status = m_proto.getStatus(NULL, 0); if (status) { /* If a command is finished, so are we. */ return EALREADY; } } int len; const char *p; while ((p = m_proto.getOutput(&len)) != NULL) { DPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd fd %d put '%s'\n", m_cfd, p)); puterr = m_obuf.put(p, len); if (puterr == EWOULDBLOCK) { DPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: put returns %d?\n", puterr)); break; } else if (puterr) { EDPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: put returns %d?\n", puterr)); return puterr; } m_proto.advanceOutput(); } } // Try writing even if we don't know that we're writable -- // since we didn't express interest in writing before, // this should save us one notification cycle. // 'Tis easier to ask forgiveness than permission. //if (revents & POLLOUT) { if (!m_cfd_connecting) { /* Write buffer to network. */ int err = m_obuf.flushTo(m_cfd); if (err && (err != EWOULDBLOCK)) { EDPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: flushTo returns %d?\n", err)); return err; } // You must tell poller about EWOULDBLOCK -- it has no other way to // know that a socket is no longer ready for I/O! if (err == EWOULDBLOCK) m_poller->clearReadiness(m_cfd, POLLOUT); } if (m_obuf.isEmpty() && m_proto.isQuit()) { DPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: session quit, output done, please shut me down\n")); return EPIPE; } //} /* If it's time for us to open a data connection to a port on the * server, do so */ #define GETBYTE3(x) (((x) >> 24) & 255) #define GETBYTE2(x) (((x) >> 16) & 255) #define GETBYTE1(x) (((x) >> 8) & 255) #define GETBYTE0(x) ((x) & 255) struct sockaddr_in address; if (m_proto.isPortReady(&address)) { int sock; #ifdef USE_DPRINT int addr = ntohl(address.sin_addr.s_addr); int port = ntohs(address.sin_port); DPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: isPortReady TRUE, address %d.%d.%d.%d:%d\n", GETBYTE3(addr), GETBYTE2(addr), GETBYTE1(addr), GETBYTE0(addr), ntohs(port))); #endif if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { xerr = errno; EDPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: connect failed, errno %d\n", xerr)); /* Caller will call stop() */ return xerr; } if (setNonblocking(sock) == -1) { xerr = errno; EDPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: setNonblocking failed, returning %d\n", xerr)); /* Caller will call stop() */ return xerr; } m_dfd_connecting = true; m_dfd = sock; int err = m_poller->add(m_dfd, this, POLLIN|POLLOUT); if (err) { EDPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: add failed\n")); return err; } if (ephemeral_connect(sock, &address, m_local_addr)) { if (errno != EINPROGRESS) { m_dfd_connecting = false; xerr = errno; EDPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: Connect fd %d failed, returning %d\n", sock, xerr)); /* Caller will call stop() */ return xerr; } } else { DPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: Connect fd %d succeeded early\n", sock)); m_dfd_connecting = false; } DPRINT(("ftp_client_pipe_t::notifyPollEvent_cfd: socket returns dfd %d\n", m_dfd)); } return 0; } /*---------------------------------------------------------------------- The operating system has told us that one of our file descriptors is ready for I/O. Deal with it. fd and revents are from poll()'s output array or sigtimedwait(); Returns 0 on success, Unix error code on failure. If this returns an error, call shutdown() to close this session. This is normally called by the app after a call to poll() or sigtimedwait(), or internally by skedCallback(). ----------------------------------------------------------------------*/ int ftp_client_pipe_t::notifyPollEvent(Poller::PollEvent *event) { int xerr; /* error to call ftpClientDone() with */ int err; DPRINT(("ftp_client_pipe_t::notifyPollEvent(fd %d, %x): state %d\n", event->fd, event->revents, m_state)); assert(event->fd != -1); if (event->fd == m_dfd) xerr = notifyPollEvent_dfd(event); else if (event->fd == m_cfd) xerr = notifyPollEvent_cfd(event); else { EDPRINT(("ftp_client_pipe_t::notifyPollEvent: fd %d != cfd %d or dfd %d!\n", event->fd, m_cfd, m_dfd)); return EINVAL; } DPRINT(("ftp_client_pipe_t::notifyPollEvent: xerr %d\n", xerr)); /* Update interest mask for Control */ if (m_cfd != -1) { short cevents = 0; if (!m_ibuf.isFull()) cevents |= POLLIN; if (!m_obuf.isEmpty()) cevents |= POLLOUT; DPRINT(("ftp_client_pipe_t::notifyPollEvent: adding m_cfd %d, events %x\n", m_cfd, cevents)); err = m_poller->setMask(m_cfd, cevents); if (err) { EDPRINT(("ftp_client_pipe_t::notifyPollEvent: setMask failed\n")); exit(1); } } /* Update interest mask for Data */ if (m_dfd != -1) { short devents = 0; /* If we're sleeping, don't ask for events if we already have them */ if ((m_state == GETTING) || ((m_state == SLEEPING) && !m_dfd_events)) devents = POLLIN; else if (m_state == PUTTING) devents = POLLOUT; DPRINT(("ftp_client_pipe_t::notifyPollEvent: adding m_dfd %d, events %x\n", m_dfd, devents)); err = m_poller->setMask(m_dfd, devents); if (err) { EDPRINT(("ftp_client_pipe_t::notifyPollEvent: setMask failed\n")); exit(1); } } /* If no error, just return. */ if (xerr == 0) return 0; // ok to have EALREADY; that's what is returned if one channel done if (xerr == EALREADY) xerr = 0; /* Special jail for tail recursion. * Either the data channel closed, or the server has a result for us. * If both are true, notify the caller that the command is done. * Return immediately thereafter -- tail recursion is more palatable * than any other kind. If this isn't ok, we'll have to * queue callbacks somehow. */ char statusbuf[256]; int status; status = m_proto.getStatus(statusbuf, sizeof(statusbuf)); if (status && !STATUS_OK(status) && (m_dfd != -1)) { /* If command failed, close any open data connection */ DPRINT(("ftp_client_pipe_t::notifyPollEvent:notify_app: closing dfd %d\n", m_dfd)); m_dfd_connecting = false; int dfd = m_dfd; m_dfd = -1; m_poller->del(dfd); close(dfd); } DPRINT(("ftp_client_pipe_t::notifyPollEvent:notify_app: status %d, dfd_conn %d, dfd %d, xerr %d\n", status, m_dfd_connecting, m_dfd, xerr)); if (xerr || (status && (m_dfd == -1))) { /* warning: this usually calls notifyPollEvent, i.e. recursion! */ call_ftpCmdDone(xerr, status, statusbuf); /* no statements after here. */ } return 0; } /*---------------------------------------------------------------------- Callback function. When the specified time has elapsed, Sked::runAll calls this method, which takes care of any read request posted by a call to notifyPollEvent while we were asleep. Any errors that happen during this call are reported via ftpCommandDone(). ----------------------------------------------------------------------*/ void ftp_client_pipe_t::skedCallback(clock_t now) { switch (m_state) { case IDLE: if (m_cfd_connecting) { EDPRINT(("ftp_client_pipe_t::skedCallback: connect timeout, calling shutdown()\n")); shutdownAndNotify(); return; } EDPRINT(("ftp_client_pipe_t::skedCallback: command timeout, calling shutdown(), id %d\n", m_datainterface->getID())); shutdownAndNotify(); break; case GETTING: EDPRINT(("ftp_client_pipe_t::skedCallback: get timeout, calling shutdown()\n")); shutdownAndNotify(); break; case SLEEPING: DPRINT(("ftp_client_pipe_t::skedCallback: waking up fd %d, events %x\n", m_dfd, m_dfd_events)); GOTO_STATE("skedCallback", GETTING); if (m_dfd_events) { short events = m_dfd_events; m_dfd_events = 0; assert(!eclock_after(m_wakeup, now)); m_wakeup = now; /* prevent resleep just in case */ Poller::PollEvent event; event.fd = m_dfd; event.revents = events; event.client = 0; int err = notifyPollEvent(&event); if (err) { EDPRINT(("ftp_client_pipe_t::skedCallback: errno %d waking up, calling shutdown()\n", err)); shutdownAndNotify(); } } break; default: assert(false); } } int ftp_client_pipe_t::kickstart(void) { Poller::PollEvent event; event.fd = m_cfd; event.revents = POLLOUT|KICKSTART; event.client = 0; return notifyPollEvent(&event); } /*--------------------------------------------------------------------- Log in to the server. Call getStatus() periodically until it returns nonzero to get whether this command succeeded. ---------------------------------------------------------------------*/ int ftp_client_pipe_t::login(const char *username, const char *password) { DPRINT(("ftp_client_pipe_t::login(%s, %s)\n", username, password)); int err = m_proto.login(username, password); if (err) return err; return kickstart(); } /*--------------------------------------------------------------------- Log out from the server. This triggers the QUIT command. Call getStatus() periodically until it returns nonzero to get whether this command succeeded. ---------------------------------------------------------------------*/ int ftp_client_pipe_t::quit() { DPRINT(("ftp_client_pipe_t::quit()\n")); int err = m_proto.quit(); if (err) return err; return kickstart(); } /*--------------------------------------------------------------------- Change directories. If dir is "..", the CDUP command is used instead of CD. Call getStatus() periodically until it returns nonzero to get whether this command succeeded. ---------------------------------------------------------------------*/ int ftp_client_pipe_t::cd(const char *dir) { DPRINT(("ftp_client_pipe_t::cd(%s)\n", dir)); int err = m_proto.cd(dir); if (err) return err; return kickstart(); } /*--------------------------------------------------------------------- Set the transfer type. Call getStatus() periodically until it returns nonzero to get whether this command succeeded, then parse the results out of the status buffer. ---------------------------------------------------------------------*/ int ftp_client_pipe_t::type(const char *ttype) { DPRINT(("ftp_client_pipe_t::type(%s)\n", ttype)); int err = m_proto.type(ttype); if (err) return err; return kickstart(); } /*--------------------------------------------------------------------- Retrieve file size. Call getStatus() periodically until it returns nonzero to get whether this command succeeded, then parse the results out of the status buffer. ---------------------------------------------------------------------*/ int ftp_client_pipe_t::size(const char *fname) { DPRINT(("ftp_client_pipe_t::size(%s)\n", fname)); int err = m_proto.size(fname); if (err) return err; return kickstart(); } /*--------------------------------------------------------------------- List the given directory's contents. If dirname is NULL, the current directory is listed. If passive is true, PASV mode is used, else PORT mode is used. datainterface->ftpCmdDone() will be called when done. ---------------------------------------------------------------------*/ int ftp_client_pipe_t::ls(const char *dirname, bool passive) { DPRINT(("ftp_client_pipe_t::ls(%s,%d)\n", dirname, passive)); /* not implemented */ assert(passive); (void) passive; int err = m_proto.ls(dirname, 0); if (err) return err; GOTO_STATE("ls", GETTING); return kickstart(); } /*--------------------------------------------------------------------- Retrieve the given file's contents. If passive is true, PASV mode is used, else PORT mode is used. datainterface->ftpCmdDone() will be called when done. ---------------------------------------------------------------------*/ int ftp_client_pipe_t::get(const char *fname, bool passive) { DPRINT(("ftp_client_pipe_t::get(%s,%d)\n", fname, passive)); /* not implemented */ assert(passive); (void) passive; int err = m_proto.get(fname, NULL); if (err) return err; GOTO_STATE("get", GETTING); m_bytesUnsleptFor = 0; m_wakeup = eclock(); /* now */ /* Abort if no response in five seconds */ m_sked->addClient(this, m_wakeup + (5 * eclock_hertz())); return kickstart(); } /* other methods' implementations would go here, but haven't been written yet */