1 /*
2  * fxp.c -- transfer files between hosts
3  *
4  * Yet Another FTP Client
5  * Copyright (C) 1998-2001, Martin Hedenfalk <mhe@stacken.kth.se>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version. See COPYING for more details.
11  */
12 
13 #include "syshdr.h"
14 #include "ftp.h"
15 #include "input.h"
16 #include "transfer.h"
17 #include "gvars.h"
18 #include "commands.h"
19 #include "strq.h"
20 #include "shortpath.h"
21 #include "utils.h"
22 
23 #ifdef HAVE_REGEX_H
24 # include <regex.h>
25 #endif
26 
27 #define FXP_INTERACTIVE 1
28 #define FXP_APPEND (1 << 1)
29 #define FXP_PRESERVE (1 << 2)
30 #define FXP_PARENTS (1 << 3)
31 #define FXP_RECURSIVE (1 << 4)
32 #define FXP_VERBOSE (1 << 5)
33 #define FXP_FORCE (1 << 6)
34 #define FXP_FORCE_NEWER (1 << 19)
35 #define FXP_NEWER (1 << 7)
36 #define FXP_DELETE_AFTER (1 << 8)
37 #define FXP_UNIQUE (1 << 9)
38 #define FXP_RESUME (1 << 10)
39 #define FXP_TAGGED (1 << 11)
40 #define FXP_NOHUP (1 << 12)
41 #define FXP_SKIP_EXISTING (1 << 13)
42 #define FXP_BACKGROUND (1 << 14)
43 #define FXP_ASCII (1 << 15)
44 #define FXP_BINARY (1 << 16)
45 #define FXP_OUTPUT_FILE (1 << 17)
46 #define FXP_SKIP_EMPTY (1 << 18)
47 
48 static Ftp *fxp_target = 0;
49 static bool fxp_batch = false;
50 static bool fxp_owbatch = false;
51 static bool fxp_delbatch = false;
52 static bool fxp_quit = false;
53 static bool fxp_skip_empty = false;
54 
55 static char *fxp_glob_mask = 0;
56 static char *fxp_dir_glob_mask = 0;
57 #ifdef HAVE_REGEX
58 static regex_t fxp_rx_mask;
59 static bool fxp_rx_mask_set = false;
60 static regex_t fxp_dir_rx_mask;
61 static bool fxp_dir_rx_mask_set = false;
62 #endif
63 
64 /* in get.c */
65 extern int get_sort_func(const void *a, const void *b);
66 
print_fxp_syntax(void)67 static void print_fxp_syntax(void)
68 {
69 	show_help(_("Transfers files between two remote servers."), "fxp [options] files",
70 	  _("  -a, --append         append if destination exists\n"
71 			"  -D, --delete-after   delete source file after successful transfer\n"
72 			"      --dir-mask=GLOB  enter only directories matching GLOB pattern\n"
73 			"      --dir-rx-mask=REGEXP\n"
74 			"                       enter only directories matching REGEXP pattern\n"
75 			"  -f, --force          overwrite existing destinations, never prompt\n"
76       "  -F, --force-newer    do not use cached information with --newer\n"
77 			"  -e, --skip-empty     skip empty files\n"
78 			"  -H, --nohup          transfer files in background (nohup mode), quits yafc\n"
79 			"  -i, --interactive    prompt before each transfer\n"
80 			"  -L, --logfile=FILE   use FILE as logfile instead of ~/.yafc/nohup/nohup.<pid>\n"
81 			"  -m, --mask=GLOB      only transfer files matching GLOB pattern\n"
82 			"  -M, --rx-mask=REGEXP only transfer files matching REGEXP pattern\n"
83 			"  -n, --newer          transfer file if destination is newer than source file\n"
84 			"  -o, --output=DEST    store in destination file/directory DEST\n"
85 			"  -p, --preserve       try to preserve file attributes\n"
86 			"  -P, --parents        append source path to destination\n"
87 			"  -q, --quiet          overrides --verbose\n"
88 			"  -r, --recursive      transfer directories recursively\n"
89 			"  -R, --resume         resume broken download (restart at eof)\n"
90 			"  -s, --skip-existing  skip file if destination exists\n"
91 			"  -t, --tagged         transfer tagged files\n"
92 			"  -T, --target=HOST    specify target host\n"
93 			"      --type=TYPE      specify transfer type, 'ascii' or 'binary'\n"
94 			"  -u, --unique         store in unique filename (if target supports STOU)\n"
95 			"  -v, --verbose        explain what is being done\n"));
96 }
97 
fxp_exclude_func(rfile * f)98 static bool fxp_exclude_func(rfile *f)
99 {
100 	if(risdotdir(f))
101 		return true;
102 	if(fxp_skip_empty == true && !risdir(f) && f->size == 0)
103 	   return true;
104 	return false;
105 }
106 
fxp_preserve_attribs(const rfile * fi,char * dest)107 static void fxp_preserve_attribs(const rfile *fi, char *dest)
108 {
109 	mode_t m = rfile_getmode(fi);
110 	if(m != (mode_t)-1) {
111 		if(ftp->has_site_chmod_command)
112 			ftp_chmod(dest, get_mode_string(m));
113 	}
114 }
115 
do_the_fxp(Ftp * srcftp,const char * src,Ftp * destftp,const char * dest,fxpmode_t how,unsigned opt)116 static int do_the_fxp(Ftp *srcftp, const char *src,
117 					  Ftp *destftp, const char *dest,
118 					  fxpmode_t how, unsigned opt)
119 {
120 	transfer_mode_t type;
121 
122 	if(test(opt, FXP_NOHUP))
123 		fprintf(stderr, "%s\n", src);
124 
125 	type = ascii_transfer(src) ? tmAscii : gvDefaultType;
126 	if(test(opt, FXP_ASCII))
127 		type = tmAscii;
128 	else if(test(opt, FXP_BINARY))
129 		type = tmBinary;
130 
131 #if 0 && (defined(HAVE_SETPROCTITLE) || defined(linux))
132 	if(gvUseEnvString && ftp_connected())
133 		setproctitle("%s, fxp %s", srcftp->url->hostname, src);
134 #endif
135 	if(test(opt, FXP_VERBOSE)) {
136 		printf("%s\n", src);
137 	}
138 	const int r = ftp_fxpfile(srcftp, src, destftp, dest, how, type);
139 #if 0 && (defined(HAVE_SETPROCTITLE) || defined(linux))
140 	if(gvUseEnvString && ftp_connected())
141 		setproctitle("%s", srcftp->url->hostname);
142 #endif
143 
144 	if(test(opt, FXP_NOHUP)) {
145 		if(r == 0)
146 			transfer_mail_msg(_("sent %s\n"), src);
147 		else
148 			transfer_mail_msg(_("failed to send %s: %s\n"),
149 							  src, ftp_getreply(false));
150 	}
151 
152 	return r;
153 }
154 
fxpfile(const rfile * fi,unsigned int opt,const char * output,const char * destname)155 static int fxpfile(const rfile *fi, unsigned int opt,
156 					const char *output, const char *destname)
157 {
158 	fxpmode_t how = fxpNormal;
159 	bool file_exists = false;
160 	char *dest, *dpath;
161 	bool dir_created;
162 	char *dest_dir, *q_dest_dir;
163 	Ftp *thisftp = ftp;
164 
165 	if((fxp_glob_mask
166 		&& fnmatch(fxp_glob_mask, base_name_ptr(fi->path), 0) == FNM_NOMATCH)
167 #ifdef HAVE_REGEX
168 	   || (fxp_rx_mask_set
169 		   && regexec(&fxp_rx_mask,
170 					  base_name_ptr(fi->path), 0, 0, 0) == REG_NOMATCH)
171 #endif
172 		)
173 		return 0;
174 
175 	if(!output)
176 		output = ".";
177 
178 	if(test(opt, FXP_PARENTS)) {
179 		char *p = base_dir_xptr(fi->path);
180 		if (asprintf(&dest, "%s/%s/%s", output, p, base_name_ptr(fi->path)) == -1)
181     {
182       fprintf(stderr, _("Failed to allocate memory.\n"));
183       free(p);
184       return -1;
185     }
186 		free(p);
187 	} else if(test(opt, FXP_OUTPUT_FILE))
188 		dest = xstrdup(output);
189 	else
190 		if (asprintf(&dest, "%s/%s", output, base_name_ptr(fi->path)) == -1)
191     {
192       fprintf(stderr, _("Failed to allocate memory.\n"));
193       return -1;
194     }
195 
196 	path_collapse(dest);
197 
198 	ftp_use(fxp_target);
199 
200 	/* make sure destination directory exists */
201 	dpath = base_dir_xptr(dest);
202 	dest_dir = ftp_path_absolute(dpath);
203 	q_dest_dir = backslash_quote(dest_dir);
204 	int r = ftp_mkpath(q_dest_dir);
205 	free(q_dest_dir);
206 	free(dest_dir);
207 	if(r == -1) {
208 		transfer_mail_msg(_("Couldn't create directory: %s\n"), dest_dir);
209 		free(dpath);
210 		free(dest);
211 		ftp_use(thisftp);
212 		return -1;
213 	}
214 	dir_created = (r == 1);
215 
216 	if(!dir_created && !test(opt, FXP_UNIQUE) && !test(opt, FXP_FORCE)) {
217 		rfile *f;
218 		f = ftp_get_file(dest);
219 		file_exists = (f != 0);
220 		if(f && risdir(f)) {
221 			/* can't overwrite a directory */
222 			printf(_("%s: is a directory\n"), dest);
223 			free(dest);
224 			return 0;
225 		}
226 	}
227 
228 	if(test(opt, FXP_APPEND)) {
229 		how = fxpAppend;
230 	} else if(file_exists) {
231 		if(test(opt, FXP_SKIP_EXISTING)) {
232 			char* sp = shortpath(dest, 42, ftp->homedir);
233 			printf(_("Remote file '%s' exists, skipping...\n"), sp);
234 			free(sp);
235 			free(dest);
236 			ftp_use(thisftp);
237 			return 0;
238 		}
239 		else if(test(opt, FXP_NEWER)) {
240 			time_t src_ft;
241 			time_t dst_ft;
242 
243 			ftp_use(thisftp);
244 			src_ft = ftp_filetime(fi->path, test(opt, FXP_FORCE_NEWER));
245 			ftp_use(fxp_target);
246 
247 			dst_ft = ftp_filetime(dest, test(opt, FXP_FORCE_NEWER));
248 
249 			if(src_ft != (time_t)-1 && dst_ft != (time_t)-1 && dst_ft >= src_ft) {
250 				char* sp = shortpath(dest, 42, ftp->homedir);
251 				printf(_("Remote file '%s' is newer than local, skipping...\n"), sp);
252 				free(sp);
253 				free(dest);
254 				ftp_use(thisftp);
255 				return 0;
256 			}
257 		}
258 		else if(!test(opt, FXP_RESUME)) {
259 			if(!fxp_owbatch) {
260 				char* sp = shortpath(dest, 42, ftp->homedir);
261 				int a = ask(ASKYES|ASKNO|ASKUNIQUE|ASKCANCEL|ASKALL|ASKRESUME,
262 							ASKRESUME,
263 							_("File '%s' exists, overwrite?"),
264 							sp);
265 				free(sp);
266 				if(a == ASKCANCEL) {
267 					fxp_quit = true;
268 					free(dest);
269 					ftp_use(thisftp);
270 					return 0;
271 				}
272 				else if(a == ASKNO) {
273 					free(dest);
274 					ftp_use(thisftp);
275 					return 0;
276 				}
277 				else if(a == ASKUNIQUE)
278 					opt |= FXP_UNIQUE; /* for this file only */
279 				else if(a == ASKALL)
280 					fxp_owbatch = true;
281 				else if(a == ASKRESUME)
282 					opt |= FXP_RESUME; /* for this file only */
283 				/* else a == ASKYES */
284 			}
285 		}
286 	}
287 
288 	if(test(opt, FXP_RESUME))
289 		how = fxpResume;
290 	if(test(opt, FXP_UNIQUE))
291 		how = fxpUnique;
292 
293 	r = do_the_fxp(thisftp, fi->path, fxp_target, dest, how, opt);
294 	free(dest);
295 	if(r != 0) {
296 		ftp_use(thisftp);
297 		return -1;
298 	}
299 
300 	if(test(opt, FXP_PRESERVE))
301 		fxp_preserve_attribs(fi, dest);
302 
303 	if(test(opt, FXP_DELETE_AFTER)) {
304 		bool dodel = false;
305 
306 		ftp_use(thisftp);
307 
308 		if(!test(opt, FXP_FORCE)
309 		   && !fxp_delbatch && !gvSighupReceived)
310 			{
311 				char* sp = shortpath(fi->path, 42, ftp->homedir);
312 				int a = ask(ASKYES|ASKNO|ASKCANCEL|ASKALL, ASKYES,
313 							_("Delete remote file '%s'?"),
314 							sp);
315 				free(sp);
316 				if(a == ASKALL) {
317 					fxp_delbatch = true;
318 					dodel = true;
319 				}
320 				else if(a == ASKCANCEL)
321 					fxp_quit = true;
322 				else if(a != ASKNO)
323 					dodel = true;
324 			} else
325 				dodel = true;
326 
327 		if(dodel) {
328 			ftp_unlink(fi->path);
329 			char* sp = shortpath(fi->path, 42, ftp->homedir);
330 			if(ftp->code == ctComplete)
331 				fprintf(stderr, _("%s: deleted\n"), sp);
332 			else
333 				fprintf(stderr, _("error deleting '%s': %s\n"), sp,
334 						ftp_getreply(false));
335 			free(sp);
336 		}
337 	}
338 
339 	ftp_use(thisftp);
340 	return 0;
341 }
342 
fxpfiles(list * gl,unsigned int opt,const char * output)343 static void fxpfiles(list *gl, unsigned int opt, const char *output)
344 {
345 	listitem *li;
346 	rfile *fp, *lnfp;
347 	const char *opath, *ofile;
348 	char *link = 0;
349 
350 	list_sort(gl, get_sort_func, false);
351 
352 	li = gl->first;
353 	while(li && !fxp_quit) {
354 		fp = (rfile *)li->data;
355 
356 		if(!ftp_connected())
357 			return;
358 
359 		if(gvSighupReceived) {
360 			if(!test(opt, FXP_RESUME))
361 				opt |= FXP_UNIQUE;
362 			opt |= FXP_FORCE;
363 		}
364 
365 		opath = fp->path;
366 		ofile = base_name_ptr(opath);
367 
368 		if(strcmp(ofile, ".")==0 || strcmp(ofile, "..")==0) {
369 			transfer_nextfile(gl, &li, true);
370 			continue;
371 		}
372 
373 		if(test(opt, FXP_INTERACTIVE) && !fxp_batch && !gvSighupReceived) {
374 			char* sp = shortpath(opath, 42, ftp->homedir);
375 			int a = ask(ASKYES|ASKNO|ASKCANCEL|ASKALL, ASKYES,
376 						_("Get '%s'?"),
377 						sp);
378 			free(sp);
379 			if(a == ASKNO) {
380 				transfer_nextfile(gl, &li, true);
381 				continue;
382 			}
383 			if(a == ASKCANCEL) {
384 				fxp_quit = true;
385 				break;
386 			}
387 			if(a == ASKALL)
388 				fxp_batch = true;
389 			/* else a==ASKYES */
390 		}
391 
392 		if(rislink(fp)) {
393 			link_to_link__duh:
394 			{
395 				char *xcurdir = base_dir_xptr(opath);
396 				link = path_absolute(fp->link, xcurdir, ftp->homedir);
397 				stripslash(link);
398 				free(xcurdir);
399 				ftp_trace("found link: '%s' -> '%s'\n", opath, link);
400 			}
401 
402 			lnfp = ftp_get_file(link);
403 			if(lnfp == 0) {
404 				/* couldn't dereference the link, try to RETR it */
405 				ftp_trace("unable to dereference link\n");
406 				int r = fxpfile(fp, opt, output, ofile);
407 				transfer_nextfile(gl, &li, r == 0);
408 				continue;
409 			}
410 
411 			if(strncmp(opath, lnfp->path, strlen(lnfp->path)) == 0) {
412 				ftp_trace("opath == '%s', lnfp->path == '%s'\n", opath,
413 						  lnfp->path);
414 				char* sp = shortpath(lnfp->path, 42, ftp->homedir);
415 				fprintf(stderr, _("%s: circular link -- skipping\n"), sp);
416 				free(sp);
417 				transfer_nextfile(gl, &li, true);
418 				continue;
419 			}
420 
421 			fp = lnfp;
422 
423 			if(rislink(fp))
424 				/* found a link pointing to another link
425 				 */
426 				goto link_to_link__duh;
427 		}
428 
429 		if(risdir(fp)) {
430 			if(test(opt, FXP_RECURSIVE)) {
431 				if((fxp_dir_glob_mask
432 					&& fnmatch(fxp_dir_glob_mask,
433 							   base_name_ptr(fp->path),
434 							   FNM_EXTMATCH) == FNM_NOMATCH)
435 #ifdef HAVE_REGEX
436 				   || (fxp_dir_rx_mask_set
437 					   && regexec(&fxp_dir_rx_mask, base_name_ptr(fp->path),
438 								  0, 0, 0) == REG_NOMATCH)
439 #endif
440 					)
441 					{
442 						/*printf("skipping %s\n", fp->path);*/
443 					} else {
444             char* recurs_output = NULL;
445             bool success = true;
446 						if(!test(opt, FXP_PARENTS))
447 							success = asprintf(&recurs_output, "%s/%s",
448 									 output ? output : ".", ofile) != -1;
449 						else
450 							success = asprintf(&recurs_output, "%s",
451 									 output ? output : ".") != -1;
452             if (!success)
453             {
454               fprintf(stderr, _("Failed to allocate memory.\n"));
455               transfer_nextfile(gl, &li, true);
456 			        continue;
457             }
458 
459             char* recurs_mask = NULL;
460             if (asprintf(&recurs_mask, "%s/*", opath) == -1)
461             {
462               free(recurs_output);
463               fprintf(stderr, _("Failed to allocate memory.\n"));
464               transfer_nextfile(gl, &li, true);
465 			        continue;
466             }
467 
468 						char* q_recurs_mask = backslash_quote(recurs_mask);
469             list* rgl = rglob_create();
470 						rglob_glob(rgl, q_recurs_mask, true, true,
471 								   fxp_exclude_func);
472 						free(q_recurs_mask);
473 						if(list_numitem(rgl) > 0)
474 							fxpfiles(rgl, opt, recurs_output);
475 						if(test(opt, FXP_PRESERVE))
476 							fxp_preserve_attribs(fp, recurs_output);
477 						rglob_destroy(rgl);
478 						free(recurs_output);
479 					}
480 			} else if(test(opt, FXP_VERBOSE)) {
481 				char* sp = shortpath(opath, 42, ftp->homedir);
482 				fprintf(stderr, _("%s: omitting directory\n"), sp);
483 				free(sp);
484 			}
485 			transfer_nextfile(gl, &li, true);
486 			continue;
487 		}
488 		if(!risreg(fp)) {
489 			if(test(opt, FXP_VERBOSE)) {
490 				char* sp = shortpath(opath, 42, ftp->homedir);
491 				fprintf(stderr, _("%s: not a regular file\n"), sp);
492 				free(sp);
493 			}
494 			transfer_nextfile(gl, &li, true);
495 			continue;
496 		}
497 		const int r = fxpfile(fp, opt, output, ofile);
498 
499 		transfer_nextfile(gl, &li, r == 0);
500 
501 		if(gvInterrupted) {
502 			gvInterrupted = false;
503 			if(li && !fxp_quit && ftp_connected() && !gvSighupReceived)
504 			{
505 				int a = ask(ASKYES|ASKNO, ASKYES,
506 							_("Continue transfer?"));
507 				if(a == ASKNO) {
508 					fxp_quit = true;
509 					break;
510 				}
511 				/* else a == ASKYES */
512 				fprintf(stderr, _("Excellent!!!\n"));
513 			}
514 		}
515 	}
516 }
517 
cmd_fxp(int argc,char ** argv)518 void cmd_fxp(int argc, char **argv)
519 {
520 	list *gl;
521 	listitem *fxp_tmp = 0;
522 	char *logfile = 0;
523 	char *fxp_output = 0;
524 #ifdef HAVE_REGEX
525 	int ret;
526 	char fxp_rx_errbuf[129];
527 #endif
528 	int c, opt = FXP_VERBOSE;
529 	struct option longopts[] = {
530 		{"append", no_argument, 0, 'a'},
531 		{"delete-after", no_argument, 0, 'D'},
532 		{"dir-mask", required_argument, 0, '3'},
533 #ifdef HAVE_REGEX
534 		{"dir-rx-mask", required_argument, 0, '4'},
535 #endif
536 		{"force", no_argument, 0, 'f'},
537     {"force-newer", no_argument, 0, 'F'},
538 		{"nohup", no_argument, 0, 'H'},
539 		{"interactive", no_argument, 0, 'i'},
540 		{"logfile", required_argument, 0, 'L'},
541 		{"mask", required_argument, 0, 'm'},
542 #ifdef HAVE_REGEX
543 		{"rx-mask", required_argument, 0, 'M'},
544 #endif
545 		{"newer", no_argument, 0, 'n'},
546 		{"output", required_argument, 0, 'o'},
547 		{"preserve", no_argument, 0, 'p'},
548 		{"parents", no_argument, 0, 'P'},
549 		{"quiet", no_argument, 0, 'q'},
550 		{"recursive", no_argument, 0, 'r'},
551 		{"resume", no_argument, 0, 'R'},
552 		{"skip-existing", no_argument, 0, 's'},
553 		{"tagged", no_argument, 0, 't'},
554 		{"target", required_argument, 0, 'T'},
555 		{"type", required_argument, 0, '1'},
556 		{"unique", no_argument, 0, 'u'},
557 		{"verbose", no_argument, 0, 'v'},
558 		{"help", no_argument, 0, 'h'},
559 		{0, 0, 0, 0}
560 	};
561 
562 	if(fxp_glob_mask) {
563 		free(fxp_glob_mask);
564 		fxp_glob_mask = 0;
565 	}
566 	if(fxp_dir_glob_mask) {
567 		free(fxp_dir_glob_mask);
568 		fxp_dir_glob_mask = 0;
569 	}
570 #ifdef HAVE_REGEX
571 	if(fxp_rx_mask_set) {
572 		fxp_rx_mask_set = 0;
573 	}
574 	if(fxp_dir_rx_mask_set) {
575 		regfree(&fxp_dir_rx_mask);
576 		fxp_dir_rx_mask_set = 0;
577 	}
578 #endif
579 
580 	if(list_numitem(gvFtpList) == 2) {
581 		fxp_tmp = gvFtpList->first;
582 		if(fxp_tmp->data == ftp)
583 			fxp_target = fxp_tmp->next->data;
584 		else
585 			fxp_target = fxp_tmp->data;
586 	} else
587 		fxp_target = 0;
588 
589 	fxp_skip_empty = false;
590 
591 	optind = 0; /* force getopt() to re-initialize */
592 	while((c=getopt_long(argc, argv, "aDefFHiL:M:no:pPqrRstT:uvh",
593 						 longopts, 0)) != EOF)
594 		{
595 			switch(c) {
596 			case 'a': /* --append */
597 				opt |= FXP_APPEND;
598 				break;
599 			case 'D': /* --delete-after */
600 				opt |= FXP_DELETE_AFTER;
601 				break;
602 			case 'f': /* --force */
603 				opt |= FXP_FORCE;
604 				break;
605       case 'F':
606         opt |= FXP_FORCE_NEWER;
607         break;
608 			   case 'e': /* --skip-empty */
609 				  opt |= FXP_SKIP_EMPTY;
610 				  fxp_skip_empty = true;
611 				  break;
612 			case '3': /* --dir-mask=GLOB */
613 				free(fxp_dir_glob_mask);
614 				fxp_dir_glob_mask = xstrdup(optarg);
615 				unquote(fxp_dir_glob_mask);
616 				break;
617 #ifdef HAVE_REGEX
618 			case '4': /* --dir-rx-mask=REGEXP */
619 				if(fxp_dir_rx_mask_set) {
620 					regfree(&fxp_dir_rx_mask);
621 					fxp_dir_rx_mask_set = false;
622 				}
623 				unquote(optarg);
624 				ret = regcomp(&fxp_dir_rx_mask, optarg, REG_EXTENDED);
625 				if(ret != 0) {
626 					regerror(ret, &fxp_dir_rx_mask, fxp_rx_errbuf, 128);
627 					ftp_err(_("Regexp '%s' failed: %s\n"),
628 							optarg, fxp_rx_errbuf);
629 					return;
630 				} else
631 					fxp_dir_rx_mask_set = true;
632 				break;
633 #endif
634 			case 'H': /* --nohup */
635 				opt |= FXP_NOHUP;
636 				break;
637 			case 'i': /* --interactive */
638 				opt |= FXP_INTERACTIVE;
639 				break;
640 			case 'L': /* --logfile=FILE */
641 				free(logfile);
642 				logfile = xstrdup(optarg);
643 				unquote(logfile);
644 				break;
645 			case 'm': /* --mask=GLOB */
646 				free(fxp_glob_mask);
647 				fxp_glob_mask = xstrdup(optarg);
648 				unquote(fxp_glob_mask);
649 				break;
650 #ifdef HAVE_REGEX
651 			case 'M': /* --rx-mask=REGEXP */
652 				if(fxp_rx_mask_set) {
653 					regfree(&fxp_rx_mask);
654 					fxp_rx_mask_set = false;
655 				}
656 
657 				unquote(optarg);
658 				ret = regcomp(&fxp_rx_mask, optarg, REG_EXTENDED);
659 				if(ret != 0) {
660 					regerror(ret, &fxp_rx_mask, fxp_rx_errbuf, 128);
661 					ftp_err(_("Regexp '%s' failed: %s\n"),
662 							optarg, fxp_rx_errbuf);
663 					return;
664 				} else
665 					fxp_rx_mask_set = true;
666 				break;
667 #endif
668 			case 'n': /* --newer */
669 				opt |= FXP_NEWER;
670 				break;
671 			case 'o': /* --output=DIRECTORY */
672 				if(fxp_target == 0) {
673 					printf(_("FxP target not set, use --target=NAME"
674 							 " (as first option)\n"));
675 					return;
676 				}
677 				fxp_output = tilde_expand_home(optarg, fxp_target->homedir);
678 				stripslash(fxp_output);
679 				unquote(fxp_output);
680 				break;
681 			case 'p': /* --preserve */
682 				opt |= FXP_PRESERVE;
683 				break;
684 			case 'P': /* --parents */
685 				opt |= FXP_PARENTS;
686 				break;
687 			case 'q': /* --quiet */
688 				opt &= ~FXP_VERBOSE;
689 				break;
690 			case 'r': /* --recursive */
691 				opt |= FXP_RECURSIVE;
692 				break;
693 			case 'R': /* --resume */
694 				opt |= FXP_RESUME;
695 				break;
696 			case 's':
697 				opt |= FXP_SKIP_EXISTING;
698 				break;
699 			case 't': /* --tagged */
700 				opt |= FXP_TAGGED;
701 				break;
702 			case '1': /* --type=[ascii|binary] */
703 				if(strncmp(optarg, "ascii", strlen(optarg)) == 0)
704 					opt |= FXP_ASCII;
705 				else if(strncmp(optarg, "binary", strlen(optarg)) == 0)
706 					opt |= FXP_BINARY;
707 				else {
708 					printf(_("Invalid option argument --type=%s\n"), optarg);
709 					return;
710 				}
711 				break;
712 			case 'T': /* --target=HOST */
713 				fxp_tmp = ftplist_search(optarg);
714 				if(!fxp_tmp)
715 					return;
716 				fxp_target = (Ftp *)fxp_tmp->data;
717 				break;
718 			case 'u': /* --unique */
719 				opt |= FXP_UNIQUE;
720 				break;
721 			case 'v': /* --verbose */
722 				opt |= FXP_VERBOSE;
723 				break;
724 			case 'h': /* --help */
725 				print_fxp_syntax();
726 				return;
727 			case '?':
728 			default:
729 				return;
730 			}
731 		}
732 
733 	if(optind >= argc && !test(opt, FXP_TAGGED)) {
734 		minargs(optind);
735 		return;
736 	}
737 
738 	need_connected();
739 	need_loggedin();
740 
741 	if(fxp_target == 0) {
742 		ftp_err(_("No target specified, try '%s --help'"
743 				  " for more information\n"), argv[0]);
744 		return;
745 	}
746 
747 #ifdef HAVE_LIBSSH
748 	if(ftp->session || fxp_target->session) {
749 		ftp_err("FxP for SSH connections no implemented\n");
750 		return;
751 	}
752 #endif
753 
754 	gl = rglob_create();
755 	while(optind < argc) {
756 		stripslash(argv[optind]);
757 		if(rglob_glob(gl, argv[optind], true, true, fxp_exclude_func) == -1)
758 			fprintf(stderr, _("%s: no matches found\n"), argv[optind]);
759 		optind++;
760 	}
761 	if(list_numitem(gl) == 0 && !test(opt, FXP_TAGGED)) {
762 		rglob_destroy(gl);
763 		return;
764 	}
765 	if(test(opt, FXP_TAGGED)
766 	   && (!ftp->taglist || list_numitem(ftp->taglist) == 0))
767 	{
768 		printf(_("no tagged files\n"));
769 		if(list_numitem(gl) == 0) {
770 			rglob_destroy(gl);
771 			return;
772 		}
773 	}
774 
775 	fxp_quit = false;
776 	fxp_batch = fxp_owbatch = fxp_delbatch = test(opt, FXP_FORCE);
777 	if(test(opt, FXP_FORCE))
778 		opt &= ~FXP_INTERACTIVE;
779 
780 	if(fxp_output && !test(opt, FXP_RECURSIVE) && list_numitem(gl) +
781 	   (test(opt, FXP_TAGGED) ? list_numitem(ftp->taglist) : 0) == 1)
782 		{
783 			opt |= FXP_OUTPUT_FILE;
784 		}
785 
786 	gvInTransfer = true;
787 	gvInterrupted = false;
788 
789 	if(test(opt, FXP_NOHUP)) {
790 		int r = 0;
791 		pid_t pid = fork();
792 
793 		if(pid == 0) {
794 			r = transfer_init_nohup(logfile);
795 			if(r != 0)
796 				exit(0);
797 		}
798 
799 		if(r != 0)
800 			return;
801 
802 		if(pid == 0) { /* child process */
803 			transfer_begin_nohup(argc, argv);
804 
805 			if(!test(opt, FXP_FORCE) && !test(opt, FXP_RESUME))
806 				opt |= FXP_UNIQUE;
807 			opt |= FXP_FORCE;
808 
809 			if(list_numitem(gl))
810 				fxpfiles(gl, opt, fxp_output);
811 			rglob_destroy(gl);
812 
813 			if(ftp->taglist && test(opt, FXP_TAGGED))
814 				fxpfiles(ftp->taglist, opt, fxp_output);
815 
816 			free(fxp_output);
817 
818 			transfer_end_nohup();
819 		}
820 		if(pid == -1) {
821 			perror("fork()");
822 			return;
823 		}
824 		/* parent process */
825 		sleep(1);
826 		printf("%d\n", pid);
827 		input_save_history();
828 		gvars_destroy();
829 		reset_xterm_title();
830 		exit(0);
831 	}
832 
833 	if(list_numitem(gl))
834 		fxpfiles(gl, opt, fxp_output);
835 	rglob_destroy(gl);
836 
837 	if(ftp->taglist && test(opt, FXP_TAGGED))
838 		fxpfiles(ftp->taglist, opt, fxp_output);
839 
840 	free(fxp_output);
841 	gvInTransfer = false;
842 }
843