1 /*--------------------------------------------------------------------------
2  Copyright 1999, 2000 by Dan Kegel http://www.kegel.com/
3  See the file COPYING
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program; if not, write to the Free Software
17  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 --------------------------------------------------------------------------*/
19 
20 #ifndef ftp_client_pipe_h
21 #define ftp_client_pipe_h
22 
23 #include "eclock.h"
24 #include "nbbio.h"
25 #include "ftp_client_proto.h"
26 #include "Poller.h"
27 #include "Sked.h"
28 
29 #define ftp_client_pipe_LINELEN 500
30 
31 class ftp_client_pipe_datainterface_t;
32 
33 /**--------------------------------------------------------------------------
34  Module to handle the networking calls for the client side of the FTP
35  protocol.  Delegates the work of parsing and generating messages to
36  the ftp_client_protocol module.
37 --------------------------------------------------------------------------*/
38 
39 class ftp_client_pipe_t : public SkedClient, Poller::Client {
40 	enum state_t { IDLE, GETTING, SLEEPING, PUTTING };
41 	state_t m_state;
42 
43 	/// control socket's file descriptor, or -1
44 	int m_cfd;
45 
46 	/// whether nonblocking connect() in progress on cfd
47 	bool m_cfd_connecting;
48 
49 	/// data socket's file descriptor, or -1
50 	int m_dfd;
51 
52 	// whether nonblocking connect() in progress on dfd
53 	bool m_dfd_connecting;
54 
55 	/// A circular buffer for writing data to m_cfd
56 	nbbio m_obuf;
57 
58 	/// A circular buffer for reading data from m_cfd
59 	nbbio m_ibuf;
60 
61 	/// A line buffer for use with m_ibuf.readline()
62 	char m_iline[ftp_client_pipe_LINELEN+1];		// +1 for NUL
63 	/// true if unused line ready in m_iline
64 	bool m_iline_full;
65 
66 	/*
67 	 Bandwidth usage throttling.  Roughly, after each read,
68 	 a dead time is set up equal to the min desired interval for
69 	 that many bits to be read, and the next read is not allowed
70 	 to happen until that dead time has passed.
71 	 Only reads are throttled.  Only the data channel is throttled yet.
72 	 At 28kbits/sec, and MTU of 700 bytes, each TU takes 5600/28
73 	 = 200 msec.  If eclock_hertz is 100, that's 20 ticks, which is ok.
74 	 At 300kbits/sec, and MTU of 1500 bytes, each TU takes 1500*8/300
75 	 = 40 ms, or 4 ticks, which is a little too coarse for comfort.
76 	 Throttling should not be used for speeds above 64kbits/sec until we
77 	 implement fractional tick accumulation.
78 	*/
79 
80 	/// How many bytes to read per eclock tick
81 	int m_bytesPerTick;
82 	/// How many bytes to read per second
83 	int m_bytesPerSec;
84 	/// How many bytes from last read we haven't slept for
85 	int m_bytesUnsleptFor;
86 	/// scheduler to manage sleeping
87 	Sked *m_sked;
88 	/// events accumulated since we fell asleep
89 	short m_dfd_events;
90 	/// desired wake up time
91 	clock_t m_wakeup;
92 
93 	/** m_datainterface is what generates and consumes ftp payload data
94 	 * on our command; it's implemented by the app.
95 	 * We simply set up connections and call him to transfer each chunk.
96 	 */
97 	ftp_client_pipe_datainterface_t *m_datainterface;
98 
99 	/// Kludge: avoid recursive calls to m_datainterface->ftpCmdDone()
100 	int m_in_ftpCmdDone;
101 
102 	/**
103 	 Call m_datainterface->ftpCmdDone() if not already in it; avoids re-entry.
104 	 Ah, the joys of immediate callbacks.
105 	*/
106 	void call_ftpCmdDone(int xerr, int status, const char *statusbuf);
107 
108 	/// Shutdown and notify the client that we have an error.
shutdownAndNotify()109 	void shutdownAndNotify() {
110 		shutdown();
111 		call_ftpCmdDone(EPIPE, 0, "shutdown");
112 	}
113 
114 	/** a reference to the parent's poller */
115 	Poller *m_poller;
116 
117 	/** 0, or a reference to the local address to use for all connections */
118 	struct sockaddr_in *m_local_addr;
119 
120 	/** m_proto is what generates and consumes ftp control messages;
121 	 * it's controlled by the app (via our get(), put(), etc. wrappers).
122 	 * We simply ferry those messages to and from the server.
123 	 */
124 	ftp_client_proto_t m_proto;
125 
126 	/**----------------------------------------------------------------------
127 	 Callback function.
128 	 When the specified time has elapsed, Sked::runAll calls this method,
129 	 which takes care of any read request posted by a call to handle_io
130 	 while we were asleep.  Any errors that happen during this call
131 	 are reported via ftpCommandDone().
132 	----------------------------------------------------------------------*/
133 	void skedCallback(clock_t now);
134 
135 	/// Call this to get the durn thing to start outputting stuff to server
136 	int kickstart(void);
137 
138 	/// Handle events on the data file descriptor.
139 	int notifyPollEvent_dfd(Poller::PollEvent *event);
140 
141 	/// Handle events on the control file descriptor.
142 	int notifyPollEvent_cfd(Poller::PollEvent *event);
143 
144 public:
145 	/**----------------------------------------------------------------------
146 	 Initialize this object, and mark it as not connected.
147 	 The calling object should implement the ftp_client_pipe_datainterface_t
148 	 interface and pass a pointer to itself.
149 	 The remining arguments are a pointer to the shared global scheduler,
150 	 and the maximum bytes per second to allow this client.
151 	 After each call to handle_data_read(), this module will use the
152 	 scheduler to sleep long enough to stay under the specified bandwidth.
153 	 @param local_addr if not 0, local_addr specifies the local IP address
154 	 to use for the local side of the connection.  Must be stable pointer.
155 	 ----------------------------------------------------------------------*/
156 	void init(ftp_client_pipe_datainterface_t * datainterface, Sked *sked,
157 		int max_bytes_per_sec, Poller *poller, struct sockaddr_in *local_addr);
158 
159 	/*----------------------------------------------------------------------
160 	 Initialize this object and start a connection to the given server.
161 	----------------------------------------------------------------------*/
162 	int connect(const char *hostname, int port);
163 
164 	/**----------------------------------------------------------------------
165 	 Call this when done with the session.
166 	 Closes the file descriptors.
167 	 Returns 0 on success, else unix error code.
168 	----------------------------------------------------------------------*/
169 	int shutdown();
170 
171 	/**----------------------------------------------------------------------
172 	 The operating system has told us that one of our file descriptors
173 	 is ready for I/O.  Deal with it.
174 
175 	 Returns 0 on success, Unix error code on failure.
176 	 If this returns an error, call shutdown() to close this session.
177 
178 	 This is normally called by the app after a call to poll() or
179 	 sigtimedwait(), or internally by skedCallback().
180 	----------------------------------------------------------------------*/
181 	int notifyPollEvent(Poller::PollEvent *event);
182 
183 /* Wrappers for protocol routines - these call the protocol
184  * routine, then kickstart the I/O
185  */
186 	/**---------------------------------------------------------------------
187 	 Get the status of the last login, quit, cd, ls, or get call.
188 	 If the operation is still in progress, returns 0.
189 	 If the operation has succeeded, returns a value between 200 and 299.
190 	 ASCII version of result is returned in given buffer, if buf != NULL.
191 	---------------------------------------------------------------------*/
getStatus(char * buf,size_t buflen)192 	int getStatus(char *buf, size_t buflen) { return m_proto.getStatus(buf, buflen); }
193 
194 	/**---------------------------------------------------------------------
195 	 Log in to the server.
196 	 datainterface->ftpCmdDone() will be called when done.
197 	---------------------------------------------------------------------*/
198 	int login(const char *username, const char *password);
199 
200 	/**---------------------------------------------------------------------
201 	 Log out from the server.  This triggers the QUIT command.
202 	 datainterface->ftpCmdDone() will be called when done.
203 	---------------------------------------------------------------------*/
204 	int quit();
205 
206 	/**---------------------------------------------------------------------
207 	 Change directories.
208 	 If dir is "..", the CDUP command is used instead of CD.
209 	 datainterface->ftpCmdDone() will be called when done.
210 	---------------------------------------------------------------------*/
211 	int cd(const char *dir);
212 
213 	/**---------------------------------------------------------------------
214 	 Setting transfer type.
215 	 datainterface->ftpCmdDone() will be called when done.
216 	---------------------------------------------------------------------*/
217 	int type(const char *ttype);
218 
219 	/**---------------------------------------------------------------------
220 	 Retrieve file size.
221 	 datainterface->ftpCmdDone() will be called when done; the size
222 	 of the file can be parsed out of the third parameter.
223 	---------------------------------------------------------------------*/
224 	int size(const char *fname);
225 
226 	/**---------------------------------------------------------------------
227 	 List the given directory's contents.
228 	 If dirname is NULL, the current directory is listed.
229 	 If passive is true, PASV mode is used, else PORT mode is used.
230 	 datainterface->ftpCmdDone() will be called when done.
231 	---------------------------------------------------------------------*/
232 	int ls(const char *dirname, bool passive);
233 
234 	/**---------------------------------------------------------------------
235 	 Retrieve the given file's contents.
236 	 If passive is true, PASV mode is used, else PORT mode is used.
237 	 datainterface->ftpCmdDone() will be called when done.
238 	---------------------------------------------------------------------*/
239 	int get(const char *fname, bool passive);
240 };
241 
242 /**----------------------------------------------------------------------
243  Interface that must be implemented by user code to handle FTP data
244  blocks passed to it by this module.
245 
246  @see ftp_client_pipe_t
247 ----------------------------------------------------------------------*/
248 class ftp_client_pipe_datainterface_t {
249 public:
250 	/**----------------------------------------------------------------------
251 	 Hook for application-supplied function.
252 	 ftp_client_pipe::handle_io() calls this to tell the app to do
253 	 a chunk of the current file transfer.
254 
255 	 Function must issue a single read or write on the fd (as appropriate
256 	 for the call that triggered the transfer).
257 	 If read reads zero bytes, or a fatal Unix error, the transfer is over.
258 	 When the transfer is over, this routine must return '0' without
259 	 closing the file.
260 	 If the transfer is not over, this routine must return the
261 	 number of bytes tranferred during this call, or -1 times the Unix
262 	 error code caused by read().
263 	 The app must not call any ftp_client_pipe_t method inside this callback.
264 	 On success, returns the number of bytes transferred, or 0 for EOF.
265 	 On error, returns -1 times the Unix error code.
266 	 Returning -EWOULDBLOCK will not terminate the transfer.
267 	----------------------------------------------------------------------*/
268 	virtual int handle_data_io(int fd, short revents, clock_t now) = 0;
269 
270 	/**----------------------------------------------------------------------
271 	 Hook for application-supplied function.
272 	 When any command (cd, ls, get, put, ...) finishes, this function is
273 	 called to alert the app.
274 
275 	 If xerr is zero, the local part of the command succeeded; the
276 	 server's numerical response is in status.  The app may call getStatus()
277 	 to retrieve the text of the server's response.
278 	 If xerr is nonzero, the local part of the command failed (perhaps
279 	 a data connection could not be established), and the app should
280 	 give up on this session (by calling shutdown() and starting over).
281 
282 	 The app may call ftp_client_pipe_t methods inside this callback.
283 	----------------------------------------------------------------------*/
284 	virtual void ftpCmdDone(int xerr, int status, const char *statusbuf) = 0;
285 
286 	/// Get an integer that identifies this object.  Used in log messages.
getID(void)287 	virtual int getID(void) { return 0; }
288 };
289 #endif
290