1 /* Unit self-test code */
2 
3 #include "dprint.h"
4 #include "ftp_client_pipe.h"
5 #include "Poller_poll.h"
6 #include <arpa/inet.h>
7 #include <assert.h>
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <netdb.h>
11 #include <poll.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <stdlib.h>
15 #include <sys/socket.h> 	/* for AF_INET */
16 #include <unistd.h>
17 
18 /* Whether the latest command is finished, and how */
19 bool g_done;
20 int g_xerr;
21 int g_status;
22 int g_size;
23 
check(int d,int e,int line)24 void check(int d, int e, int line) { if (d != e) {
25 		printf("check: test failed: %d != %d at line %d\n", d, e, line);
26 		exit(1);
27 	}
28 }
29 
checkne(int d,int e,int line)30 void checkne(int d, int e, int line) { if (d == e) {
31 		printf("checkne: test failed: %d == %d at line %d\n", d, e, line);
32 		exit(1);
33 	}
34 }
35 
strcheck(const char * d,const char * e,int line)36 void strcheck(const char * d, const char * e, int line)
37 {
38 	if (!d || !e) {
39 		printf("strcheck: test failed: One of the character string is null: %s %s\n", d, e);
40 		exit(1);
41 	}
42 	if (strcmp(d, e)) {
43 		printf("strcheck: test failed: %s != %s at line %d\n", d, e, line);
44 		exit(1);
45 	}
46 }
47 
statuscheck(const char * d,int e,int line)48 void statuscheck(const char * d, int e, int line)
49 {
50 	if (!d) {
51 		printf("strcheck: test failed: Character string is null\n");
52 		exit(1);
53 	}
54 	if (atoi(d) != e) {
55 		printf("statuscheck: test failed: atoi(%s) != %d at line %d\n", d, e, line);
56 		exit(1);
57 	}
58 }
59 
60 #define CHECK(d, e) check(d, e, __LINE__)
61 #define CHECKNE(d, e) checkne(d, e, __LINE__)
62 #define STRCHECK(d, e) strcheck(d, e, __LINE__)
63 #define STATUSCHECK(d, e) statuscheck(d, e, __LINE__)
64 
65 /* return whether the given status code indicates a given FTP command status */
66 #define STATUS_OK(s)  (((s) >= 200) && ((s) <= 299))
67 
68 /* We have to implement the handle_data_io method from
69  * ftp_client_pipe_datainterface_t
70  */
71 class ftp_client_pipe_test_t: public ftp_client_pipe_datainterface_t, Poller::Client
72 {
73 	ftp_client_pipe_t cp;
74 public:
75 	int handle_data_io(int fd, short revents, clock_t now);
notifyPollEvent(Poller::PollEvent * event)76 	int notifyPollEvent(Poller::PollEvent *event) {
77 		DPRINT(("test notifyPollEvent: fd %d, client %p\n", event->fd, event->client));
78 		return cp.notifyPollEvent(event);
79 	}
80 	void fetchFile(const char *hostname, const char *user, const char *pass, const char *fname);
81 	void ftpCmdDone(int xerr, int status, const char *buf);
82 };
83 
84 /*----------------------------------------------------------------------
85  User-supplied function.
86  The operating system has told us that our data file descriptor
87  is ready for I/O.  This function deals with it.
88 
89  This is called internally by handle_io().
90  It must be overridden by the user with a useful method.
91 
92  Function must issue a single read or write on the fd (as appropriate
93  for the call that triggered the transfer) with as large a buffer as
94  is practical.
95  If read reads zero bytes, the transfer is over.
96  When the transfer is over, this routine must return '0' without
97  closing the file.
98  If the transfer is not over, this routine must return the
99  number of bytes tranferred during this call.
100 ----------------------------------------------------------------------*/
handle_data_io(int fd,short revents,clock_t now)101 int ftp_client_pipe_test_t::handle_data_io(int fd, short revents, clock_t now)
102 {
103 	char buf[16384];
104 	(void) now;
105 	int nread = read(fd, buf, sizeof(buf));
106 	if (nread == 0) {
107 		DPRINT(("ftp_client_pipe_test_t::handle_data_io: fd %d, revents %x; transfer over\n", fd, revents));
108 		return 0;
109 	}
110 	if (nread == -1) {
111 		DPRINT(("ftp_client_pipe_test_t::handle_data_io: fd %d, revents %x; read fails, errno %d\n", fd, revents, errno));
112 		return 0;
113 	}
114 	DPRINT(("ftp_client_pipe_test_t::handle_data_io: fd %d, revents %x; read %d bytes\n", fd, revents, nread));
115 	//printf("Got '%.*s'\n", nread, buf);
116 	return nread;
117 }
118 
119 
120 /*----------------------------------------------------------------------
121  App-supplied function.
122  The current command just finished.
123 ----------------------------------------------------------------------*/
ftpCmdDone(int xerr,int status,const char * buf)124 void ftp_client_pipe_test_t::ftpCmdDone(int xerr, int status, const char *buf)
125 {
126 	DPRINT(("ftp_client_pipe_test_t::ftpCmdDone(%d, %d, %s)\n", xerr, status, buf));
127 	g_done = true;
128 	g_status = status;
129 	g_xerr = xerr;
130 	sscanf(buf, "%*d %d", &g_size);
131 }
132 
133 /*----------------------------------------------------------------------
134  Fetch a file from an ftp server.
135 ----------------------------------------------------------------------*/
fetchFile(const char * hostname,const char * user,const char * pass,const char * fname)136 void ftp_client_pipe_test_t::fetchFile(const char *hostname, const char *user, const char *pass, const char *fname)
137 {
138 	int err;
139 	enum state_t {LOGGING_IN,SETTING,GETTING,DONE};
140 	state_t state;
141 	Sked sk;
142 
143 	err = sk.init();
144 	if (err) {
145 		printf("main: Sked::init() failed.\n");
146 		exit(1);
147 	}
148 
149 	Poller_poll m_poller;
150 	err = m_poller.init();
151 	CHECK(err, 0);
152 
153 	cp.init(this, &sk, 56000/8, &m_poller, 0);
154 	err = cp.connect(hostname, 21);	/* 21 is standard ftp server port */
155 	CHECK(err, 0);
156 
157 	g_done = false;
158 	state = LOGGING_IN;
159 	err = cp.login(user, pass);
160 	CHECK(err, 0);
161 
162 	clock_t tix_per_second = eclock_hertz();
163 	assert(tix_per_second < 100000);	/* numerous overflows otherwise */
164 
165 	/* Start pumping data to the pipe */
166 	while (state != DONE) {
167 		/* Handle pending stuff */
168 		clock_t now = eclock();
169 		sk.runAll(now);
170 
171 		/* Did the last command finish? */
172 		if (g_done) {
173 			g_done = false;
174 			switch(state) {
175 			case LOGGING_IN:
176 				if (g_status == 230) {
177 					/* login succeeded.  */
178 					printf("main:LOGGING_IN: done.  Fetching %s\n", fname);
179 					/* Switch to binary mode. */
180 					err = cp.type("I");
181 					CHECK(err, 0);
182 					state = SETTING;
183 				} else {
184 					printf("main:LOGGING_IN: Unexpected status %d or xerr %d, test failed.\n", g_status, g_xerr);
185 					exit(1);
186 				}
187 				break;
188 
189 			case SETTING:
190 				if (g_status == 200) {
191 					/* type I succeeded.  */
192 					printf("main:SETTING: done. TYPE I\n");
193 					/* Fetch the file using passive mode. */
194 					err = cp.get(fname, true);
195 					CHECK(err, 0);
196 					state = GETTING;
197 				} else {
198 					printf("main:SETTING: Unexpected status %d or xerr %d, test failed.\n", g_status, g_xerr);
199 					exit(1);
200 				}
201 				break;
202 
203 			case GETTING:
204 				if (!g_xerr && STATUS_OK(g_status)) {
205 					printf("main: transfer succeeded\n");
206 					state = DONE;
207 					break;
208 				} else {
209 					printf("main:GETTING: Unexpected status %d or xerr %d, test failed.\n", g_status, g_xerr);
210 					exit(1);
211 				}
212 
213 			default:
214 				printf("bug\n");
215 				exit(1);
216 			}
217 		}
218 
219 		/* Call poller.waitForEvents() to find out what handles are ready for read or write */
220 		clock_t tixUntilNextEvent = sk.nextTime(now + tix_per_second) - now;
221 		int msUntilNextEvent = (tixUntilNextEvent * 1000) / tix_per_second + 1;
222 		int rfds = m_poller.waitForEvents(msUntilNextEvent);
223 		DPRINT(("main: state %d, rfds %d\n", state, rfds));
224 		if (rfds < 0) {
225 			perror("poll");
226 			exit(1);
227 		}
228 
229 		if (rfds != EWOULDBLOCK) {
230 			/* At least one handle is ready.  Deal with it. */
231 			while (1) {
232 				Poller::PollEvent event;
233 				err = m_poller.getNextEvent(&event);
234 				if (err == EWOULDBLOCK)
235 					break;
236 				CHECK(0, err);
237 				printf("main: fd %d, revents %x, state %d\n",
238 					event.fd, event.revents, state);
239 				err = event.client->notifyPollEvent(&event);
240 				if (err) {
241 					printf("main: Error %d on fd %d.  Closing.\n", err, event.fd);
242 					cp.shutdown();
243 					CHECK(0,1);
244 				}
245 			}
246 		}
247 
248 	}
249 	printf("fetchFile returning.\n");
250 }
251 
252 /* Main program to test the ftp client pipe module.
253  * This is an attended test - i.e. you have to make sure the file
254  * is actually on the given ftp server before you run it.
255  * Prints message and exits with nonzero status if any test fails.
256  */
main(int argc,char ** argv)257 int main( int argc, char **argv )
258 {
259 
260 	if (argc != 5) {
261 		printf("\
262 Usage: %s host username password filename_to_get\n\
263 Logs in to the given ftp server with the given username and password, and\n\
264 retrieves the given file.\n", argv[0]);
265 		exit(1);
266 	}
267 	ftp_client_pipe_test_t test;
268 
269 	test.fetchFile(argv[1], argv[2], argv[3], argv[4]);
270 	printf("No tests failed.\n" );
271 	return 0;
272 }
273 
274