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