"SfR Fresh" - the SfR Freeware/Shareware Archive 
As a special service "SfR Fresh" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting with prefixed line numbers.
Alternatively you can here view or download the uninterpreted source code file.
That can be also achieved for any archive member file by clicking within an archive contents listing on the first character of the file(path) respectively on the according byte size field.
1 /*
2 * Support routines for lurkftp
3 *
4 * (C) 1997 Thomas J. Moore
5 * questions/comments -> dark@mama.indstate.edu
6 *
7 * This is free software. No warranty for applicability or fitness is
8 * expressed or implied.
9 *
10 * This code may be modified and distributed freely so long as the original
11 * author is credited and any changes are documented as such.
12 *
13 */
14
15 #include "lurkftp.h"
16 #include <setjmp.h>
17 #include <dirent.h>
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 #include <sys/time.h>
21 #include <pwd.h>
22
23 /* some generic scratch buffers that prevent this */
24 /* from ever being thread-safe */
25 static char buf[4096];
26 static char name[4096];
27
28 #define ret_lo(x) do{ftp_closeconn(site);return (x);}while(0)
29
30 #define copyname(s) copystr(s,strlen(s),&dir->names)
31
32 /* read remote dir */
33 static int doftpdir(struct dirmem *dir, struct ftpsite *site,
34 const char **dirs, int ndirs, int recurse,
35 const regex_t *xfilt)
36 {
37 char *s;
38 const char *cd;
39 unsigned long i, j;
40 int noparm = 0;
41 struct fnamemem *fm = NULL; /* initialized to satisfy GCC */
42 struct fname *f;
43 int res;
44
45 if((res = ftp_openconn(site)) != ERR_OK)
46 ret_lo(res);
47 if((res = ftp_type(site, "A")) != ERR_OK)
48 ret_lo(res);
49 if(dir->count) {
50 f = lastname(&dir->files);
51 do {
52 s = buf;
53 if(**dirs != '/') {
54 if(ftp_getwd(site, buf) != ERR_OK)
55 ret_lo(ERR_PWD);
56 s = buf+strlen(buf);
57 if(s == buf || s[-1] != '/')
58 *s++ = '/';
59 }
60 strcpy(s,*dirs);
61 s += strlen(s);
62 if(s == buf || s[-1] != '/') {
63 *s++ = '/';
64 *s = 0;
65 }
66 } while(strcmp(f->root, buf) && dirs++ && --ndirs);
67 cd = f->root;
68 } else
69 cd = NULL;
70 for(;ndirs--;dirs++, cd = NULL) {
71 if(!cd) {
72 s = buf;
73 if(recurse > 1)
74 recurse -= 2;
75 if(**dirs != '/') {
76 if(ftp_getwd(site, buf) != ERR_OK)
77 ret_lo(ERR_PWD);
78 s = buf+strlen(buf);
79 if(s == buf || s[-1] != '/')
80 *s++ = '/';
81 }
82 strcpy(s,*dirs);
83 s += strlen(s);
84 if(s == buf || s[-1] != '/') {
85 *s++ = '/';
86 *s = 0;
87 }
88 cd = copyname(buf);
89 if(!cd)
90 ret_lo(ERR_MEM);
91 } else
92 recurse += 2;
93 if(!recurse) {
94 /* first try single recursive list command */
95 strcpy(buf,cd);
96 /* strip trailing / for command */
97 if(buf[1])
98 buf[strlen(buf)-1] = 0;
99 if((i = ftp_port(site)) == ERR_OK &&
100 (i=ftp_cmd2(site, "LIST -lRa ", buf)) == ERR_OK &&
101 (i=parsels(dir,(read_func)ftp_read,site,cd,cd)) == ERR_OK &&
102 dir->count > 1) {
103 /* recover already-read dirs under the assumption */
104 /* that this recovery is faster than re-FTPing the dir */
105 const char *lastdir, *curdir = NULL;
106
107 res = ftp_endport(site);
108 /* need to reread lastdir if endport returns an error */
109 if(res == ERR_OK)
110 lastdir = NULL;
111 else {
112 f = lastname(&dir->files);
113 lastdir = f->dir;
114 }
115 if(debug & DB_TRACE)
116 fprintf(stderr, "Restarting at %s\n", lastdir? lastdir: "skipped");
117 fm=dir->files;
118 f=&fm->names[NSKIP-1];
119 curdir = f->dir; /* skip root directory */
120 for(i = 0; i < dir->count; i++, f--) {
121 if(i && !(i % NSKIP)) {
122 fm = fm->nxt;
123 f = &fm->names[NSKIP-1];
124 }
125 if(f->dir == lastdir)
126 break;
127 if(f->dir != curdir) {
128 struct fnamemem *fm2;
129 struct fname *f2;
130
131 curdir = f->dir;
132 /* I wish there was a better way to do this... */
133 /* perhaps store as real tree; future enhancement */
134 fm2 = dir->files;
135 f2 = &fm2->names[NSKIP-1];
136 for(j=0; j < dir->count; j++, f2--) {
137 if(j && !(j % NSKIP)) {
138 fm2 = fm2->nxt;
139 f2 = &fm2->names[NSKIP-1];
140 }
141 if(f2->flags & FNF_DODIR &&
142 !strncmp(f2->dir, curdir, strlen(f2->dir)) &&
143 !strncmp(f2->name, curdir+strlen(f2->dir), strlen(f2->name)) &&
144 curdir[strlen(f2->dir)+strlen(f2->name)+1] == 0) {
145 if(debug & DB_TRACE)
146 fprintf(stderr, "Closing %s%s\n", f2->dir, f2->name);
147 f2->flags &= ~FNF_DODIR;
148 recurse--;
149 break;
150 }
151 }
152 }
153 if(f->flags & FNF_DODIR) {
154 if(debug & DB_TRACE)
155 fprintf(stderr, "Checking out %s%s\n", f->dir, f->name);
156 recurse++;
157 }
158 }
159 /* remove [partial] directory read */
160 if(lastdir) {
161 while(dir->count > 0 &&
162 (f = lastname(&dir->files)) &&
163 f->dir == curdir) {
164 freelastname(&dir->files);
165 dir->count--;
166 }
167 ret_lo(res);
168 }
169 /* at this point, I could ret_lo(ERR_DIRR), but maybe the */
170 /* skipped dirs really are empty, so read recursively */
171 if(recurse) /* more dirs left */
172 recurse = 2;
173 }
174 }
175 /* special hack for non-dirs */
176 /* this will force an error if non-dir isn't a symlink to a dir */
177 if(!recurse && i == ERR_OK && dir->count == 1 &&
178 (f = lastname(&dir->files)) && /* should always be true */
179 !S_ISDIR(f->mode) &&
180 !strncmp(f->dir,f->name,strlen(f->dir)-1)) {
181 recurse = 1;
182 /* remove the non-dir from listing */
183 freelastname(&dir->files);
184 dir->count--;
185 }
186 /* try manual recursion if recursive list failed */
187 if(recurse || i || !dir->count) {
188 const char *root = cd;
189
190 i = -1;
191 f = NULL;
192 while(1) {
193 /* find new dir to cd to */
194 while(++i<dir->count) {
195 if(!f) {
196 for(j=i/NSKIP,fm=dir->files;j;j--)
197 fm = fm->nxt;
198 f = &fm->names[NSKIP-1-i%NSKIP];
199 } else if(!(i % NSKIP)) {
200 fm = fm->nxt;
201 f = &fm->names[NSKIP-1];
202 } else
203 f--;
204 if(f->flags & FNF_DODIR) {
205 strcpy(buf,f->dir);
206 strcat(buf,f->name);
207 s = buf+strlen(buf);
208 if(s[-1] != '/') {
209 *s++ = '/';
210 *s = 0;
211 }
212 if(!xfilt || regexec(xfilt, buf, 0, NULL, 0)) {
213 cd = copyname(buf);
214 if(!cd)
215 ret_lo(ERR_MEM);
216 break;
217 }
218 }
219
220 }
221 if(i && i >= dir->count)
222 break;
223 /* strip trailing / for command */
224 strcpy(buf,cd);
225 if(buf[1])
226 buf[strlen(buf)-1] = 0;
227 if((j = ftp_chdir(site, buf)) == ERR_OK) {
228 if(noparm || (j = ftp_port(site)) != ERR_OK ||
229 (j = ftp_cmd(site,"list -a")) != ERR_OK) {
230 noparm = 1;
231 if((j = ftp_port(site)) == ERR_OK)
232 j = ftp_cmd(site,"list");
233 }
234 if(j == ERR_OK) {
235 if((j=parsels(dir,(read_func)ftp_read,site,cd,root)) != ERR_OK)
236 ret_lo(j);
237 if((j=ftp_endport(site)) != ERR_OK) {
238 /* remove [partial] directory read */
239 if(dir->count > 0) {
240 f = lastname(&dir->files);
241 if(!strcmp(f->dir, cd)) {
242 cd = f->dir;
243 while(dir->count > 0 &&
244 (f = lastname(&dir->files)) &&
245 f->dir == cd) {
246 freelastname(&dir->files);
247 dir->count--;
248 }
249 }
250 ret_lo(res);
251 }
252 ret_lo(j);
253 }
254 } else
255 ret_lo(j);
256 } else {
257 lockof();
258 fprintf(of, "*** %s: Skipping %s: %s\n", site->host, buf, site->lastresp);
259 unlockof();
260 }
261 /* the next stuff is in case server doesn't take parameters */
262 /* for list, but doesn't report an error */
263 if(!noparm && !dir->count)
264 noparm = 1;
265 /* at this point, the read must have been "successful", so */
266 /* just mark it as done even if it was empty */
267 else if(f)
268 f->flags &= ~FNF_DODIR;
269 }
270 }
271 }
272 i = toarray(dir);
273 if(i)
274 ret_lo(i);
275 return i;
276 }
277
278 /* read remote dir w/ retry */
279 int readftpdir(struct dirmem *dir, struct ftpsite *site, const char **dirs,
280 int ndirs, int recurse, const regex_t *xfilt)
281 {
282 int i,err = 0;
283 struct timeval rt;
284
285 for(i=site->to.retrycnt?site->to.retrycnt:-1;i;i--) {
286 if(debug & DB_TRACE)
287 fprintf(stderr,"Trying %s [#%d]\n",site->host,site->to.retrycnt-i+1);
288 if((err=doftpdir(dir, site, dirs, ndirs, recurse, xfilt)) && err != ERR_DIR) {
289 if(debug & DB_TRACE)
290 fprintf(stderr,"%s; waiting %d seconds\n",ftperr(err),site->to.retrytime);
291 rt.tv_sec = site->to.retrytime;
292 rt.tv_usec = 0;
293 select(0,NULL,NULL,NULL,&rt);
294 } else
295 break;
296 }
297 /* return specific error even when max retries */
298 /* this is so report shows exact error */
299 return !err && !i ? ERR_MAXRETR : err;
300 }
301
302 /* read remote dir from remote file w/ retry */
303 /* FIXME: this should read directly instead of using temp file */
304 int readftplsf(struct dirmem *dir, struct ftpsite *site, const char *dirs,
305 const char *rname)
306 {
307 FILE *f;
308 int err;
309 char *s;
310
311 strcpy(name,dirs);
312 if((s = strchr(name,' ')))
313 *s = 0;
314 else
315 s = name+strlen(name);
316 if(s == name || s[-1] != '/') {
317 *s++ = '/';
318 *s = 0;
319 }
320 if(!(s = copyname(name)))
321 return ERR_MEM;
322 if(!(f = tmpfile())) {
323 perror("tmp file for ftplsf");
324 return ERR_OS;
325 }
326 if((err = getfile(f,site,rname))) {
327 fclose(f);
328 return err;
329 }
330 rewind(f);
331 /* root may be inaccurate if dirs contains more than one dir, but */
332 /* root is only used by mirroring, which guarantees only one dir */
333 err = parsels(dir,(read_func)readf,f,s,s);
334 fclose(f);
335 if(err == ERR_OK)
336 err = toarray(dir);
337 if(err)
338 ret_lo(err);
339 return err;
340 }
341
342 /* retrieve a file: log in if necessary, try restart if f has data */
343 /* log off if error, else keep connection open */
344 /* return: 0 == ftp failure; -1 == write failure; 1 = success */
345 static int retrfile(FILE *f, struct ftpsite *site, const char *rf)
346 {
347 long n;
348 int ret;
349
350 if((ret = ftp_openconn(site)) != ERR_OK)
351 ret_lo(ret);
352 if((ret = ftp_type(site, "I")) != ERR_OK)
353 ret_lo(ret);
354 if((ret = ftp_port(site)) != ERR_OK)
355 ret_lo(ret);
356 if((n = ftell(f)) > 0) {
357 sprintf(buf,"%ld",n);
358 if(ftp_cmd2(site, "REST ", buf) != ERR_OK)
359 rewind(f);
360 }
361 if((ret = ftp_cmd2(site, "RETR ", rf)) != ERR_OK && ret != ERR_CMD)
362 ret_lo(ret);
363 n = 0;
364 do {
365 if(n && nosig_fwrite(buf,n,1,f) != 1) {
366 perror("fwrite");
367 ret_lo(ERR_OS);
368 }
369 } while((n=ftp_read(site,buf,4096,0)) > 0 ||
370 errno == EAGAIN || errno == EINTR);
371 if(n<0)
372 ret_lo((int)-n);
373 return ftp_endport(site); /* don't log out for next file */
374 }
375
376 /* get a file with retry */
377 int getfile(FILE *f, struct ftpsite *site, const char *fname)
378 {
379 int i,err;
380 struct timeval rt;
381
382 for(err=0,i=site->to.retrycnt?site->to.retrycnt:-1;i;i--) {
383 /* retrfile will set f to correct file position */
384 if((err=retrfile(f, site, fname)) && (err != ERR_OS || errno == EINTR) && err != ERR_CMD) {
385 if(fflush(f) && errno != EINTR) {
386 err = ERR_OS;
387 break;
388 }
389 if(debug & DB_TRACE)
390 fprintf(stderr,"%s; waiting %d seconds\n",ftperr(err),site->to.retrytime);
391 rt.tv_sec = site->to.retrytime;
392 rt.tv_usec = 0;
393 select(0,NULL,NULL,NULL,&rt);
394 } else
395 break;
396 }
397 /* always return MAXRETR if max retries reached */
398 /* no specific error will be reported, anyway */
399 /* return !i?ERR_MAXRETR:err; */
400 return !i && !err?ERR_MAXRETR:err;
401 }
402
403 /* misc functions */
404 const char *ftperr(int err)
405 {
406 switch(err) {
407 case ERR_OK:
408 return "No error";
409 case ERR_EOF:
410 return "End of File";
411 case ERR_TO:
412 return "Timed out";
413 case ERR_MEM:
414 return "Out of memory";
415 case ERR_DIR:
416 return "Nothing in directory";
417 case ERR_LOGIN:
418 return "Can't log in";
419 case ERR_PWD:
420 return "Can't get current working directory";
421 case ERR_OS:
422 return "OS error";
423 case ERR_CMD:
424 return "Error sending command";
425 case ERR_MAXRETR:
426 return "Maximum retry count reached";
427 }
428 return "Unknown error";
429 }