1 //
2 //   mir.c
3 //
4 //   Oliver Fromme  <olli@fromme.com>
5 //   @(#)$Id: mir.c,v 1.36 1998/12/04 09:04:43 olli Exp $
6 //
7 
8 static const char cvsid[]
9     = "@(#)$Id: mir.c,v 1.36 1998/12/04 09:04:43 olli Exp $";
10 
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <sys/time.h>
18 #include <dirent.h>
19 #include <limits.h>
20 #include <time.h>
21 #include <utime.h>
22 #include <errno.h>
23 extern int errno;
24 
25 #include "omi.h"
26 #include "mir.h"
27 #include "pro.h"
28 #include "rex.h"
29 
30 static int
iterate_dir(char * dir,jobfunc * job,void * jobdata)31 iterate_dir (char *dir, jobfunc *job, void *jobdata)
32 {
33 	//
34 	//   Calls the specified job function for each entry
35 	//   in the specified directory.  The result of the
36 	//   job function is ignored.
37 	//   returns 0 on success, -1 on error.
38 	//
39 
40 	DIR *mydir;
41 	struct dirent *entry;
42 	char *newpath;
43 	int dirlen;	//   length of "dir" + "/"
44 	int namelen, namespace;
45 
46 	if (!(mydir = opendir(dir))) {
47 		//
48 		//   If we're in test mode, ignore non-existent
49 		//   directories (because directories are not
50 		//   created in test mode).
51 		//
52 
53 		if ((cc->flags & MIRROR_TEST) && errno == ENOENT)
54 			return 0;
55 		fprintf (stderr, "%s: opendir(%s): %s\n", me, dir, serror());
56 		cs->errors++;
57 		return -1;
58 	}
59 	dirlen = strlen(dir) + 1;
60 	newpath = tmalloc(dirlen + 1);
61 	strcpy (newpath, dir);
62 	newpath[dirlen - 1] = '/';
63 	newpath[dirlen] = '\0';
64 	namespace = 0;
65 	while ((entry = readdir(mydir))) {
66 		//
67 		//   Be careful to ignore the "." and ".."
68 		//   entries, otherwise we might get into an
69 		//   infinite loop somewhere.
70 		//
71 
72 		if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' ||
73 		    (entry->d_name[1] == '.' && entry->d_name[2] == '\0')))
74 			continue;
75 		namelen = strlen(entry->d_name);
76 		if (namelen > namespace) {
77 			newpath = realloc(newpath, dirlen + namelen + 1);
78 			if (!newpath)
79 				out_of_memory();
80 			namespace = namelen;
81 		}
82 		strcpy (newpath + dirlen, entry->d_name);
83 		job (newpath, jobdata);
84 	}
85 	free (newpath);
86 	closedir (mydir);
87 	return 0;
88 }
89 
90 static int
time_diff(time_t ltime,time_t rtime,omifile * of)91 time_diff (time_t ltime, time_t rtime, omifile *of)
92 {
93 	//
94 	//   Compares the given local and remote time stamps.
95 	//   Returns 0 if they are equal, within the precision
96 	//   value given by of->tprec (or if the local time is
97 	//   newer than remote).
98 	//   Returns 1 if they are different, but within the
99 	//   maximum deviation (or if the IGNORETIME option
100 	//   is set).
101 	//   Returns 2 if they are completely different, and
102 	//   the IGNORETIME option is not set.
103 	//
104 
105 	time_t diff;
106 
107 	if (ltime > rtime)
108 		if (cc->flags && MIRROR_GETOLDERFILES)
109 			diff = ltime - rtime;
110 		else
111 			return 0;
112 	else
113 		diff = rtime - ltime;
114 	if (diff > of->tprec)
115 		if (!(cc->flags && MIRROR_IGNORETIME) &&
116 		    diff > of->tprec + cc->deviation)
117 			return 2;
118 		else
119 			return 1;
120 	else
121 		return 0;
122 }
123 
124 static char *
timereport(time_t t)125 timereport (time_t t)
126 {
127 	static char buff[12];
128 
129 	//   Note:  Some versions of gcc print a warning if %y
130 	//   is used.  Oh how I hate that brain-dead GNU crap.
131 
132 	if (cc->flags & MIRROR_LOCALTIME)
133 		strftime (buff, 12, "%y%m%d%H%M", localtime(&t));
134 	else
135 		strftime (buff, 12, "%y%m%d%H%M", gmtime(&t));
136 	return buff;
137 }
138 
139 static char *
modereport(mode_t m)140 modereport (mode_t m)
141 {
142 	static char buff[12];
143 
144 	strcpy (buff, "----------");
145 	if ((m & S_IFMT) == S_IFDIR)	buff[0] = 'd';
146 	if (m & S_IRUSR)	buff[1] = 'r';
147 	if (m & S_IWUSR)	buff[2] = 'w';
148 	if (m & S_IXUSR)	buff[3] = 'x';
149 	if (m & S_IRGRP)	buff[4] = 'r';
150 	if (m & S_IWGRP)	buff[5] = 'w';
151 	if (m & S_IXGRP)	buff[6] = 'x';
152 	if (m & S_IROTH)	buff[7] = 'r';
153 	if (m & S_IWOTH)	buff[8] = 'w';
154 	if (m & S_IXOTH)	buff[9] = 'x';
155 	return buff;
156 }
157 
158 #define WHY_NEW  1
159 #define WHY_GONE 2
160 #define WHY_TIME 3
161 #define WHY_SIZE 4
162 #define WHY_LINK 5
163 #define WHY_MODE 6
164 
165 static void
report(int why,long old,long new,char * name)166 report (int why, long old, long new, char *name)
167 {
168 	static omisite *lastsite = NULL;
169 	char whychar = 0;
170 	char *fname, *tmps;
171 	int rootlen;
172 
173 	if (!cc->havereporthead) {
174 		cc->havereporthead = TRUE;
175 		putc ('\n', stderr);
176 		if (lastsite != ct) {
177 			lastsite = ct;
178 			fprintf (stderr, "Report for server %s\n", ct->name);
179 		}
180 		fprintf (stderr, "Mirror %s -> %s\n",
181 		    cp->remotedir, cp->localdir);
182 	}
183 
184 	rootlen = strlen(cp->localdir);
185 	if (!strncmp(name, cp->localdir, rootlen)) {
186 		//
187 		//   Cut off the root of the
188 		//   local tree.
189 		//
190 		fname = name + rootlen;
191 		while (*fname == '/')
192 			fname++;
193 		if (!*fname)
194 			fname = ".";
195 	}
196 	else
197 		fname = name;
198 
199 	switch (why) {
200 	case WHY_LINK:
201 		fprintf (stderr, "*        <link> %10s %s\n",
202 		    timereport(new), fname);
203 		break;
204 	case WHY_NEW:
205 		whychar = '+';
206 		/* FALLTHROUGH */
207 	case WHY_GONE:
208 		if (!whychar)
209 			whychar = '-';
210 		if (old == -1)
211 			fprintf (stderr, "%c         <dir> %10s %s\n",
212 			    whychar, timereport(new), fname);
213 		else if (old == -2)
214 			fprintf (stderr, "%c        <link> %10s %s\n",
215 			    whychar, timereport(new), fname);
216 		else
217 			fprintf (stderr, "%c    %10ld %10s %s\n",
218 			    whychar, old, timereport(new), fname);
219 		break;
220 	case WHY_TIME:
221 		if (!(tmps = strdup(timereport(old))))
222 			out_of_memory();
223 		fprintf (stderr, "* %10s -> %10s %s\n",
224 		    tmps, timereport(new), fname);
225 		free (tmps);
226 		break;
227 	case WHY_SIZE:
228 		if (old < new)
229 			whychar = '>';
230 		else
231 			whychar = '<';
232 		fprintf (stderr, "%c %10ld -> %10ld %s\n",
233 		    whychar, old, new, fname);
234 	case WHY_MODE:
235 		if (!(tmps = strdup(modereport(old))))
236 			out_of_memory();
237 		fprintf (stderr, "* %10s -> %10s %s\n",
238 		    tmps, modereport(new), fname);
239 		free (tmps);
240 	}
241 }
242 
243 static bool
tonne(char * path,void * allowreport)244 tonne (char *path, void *allowreport)
245 {
246 	//
247 	//   Removes the specified files (or at least tries to).
248 	//   If "path" is a directory, tries to recursively
249 	//   remove the complete tree.  If "path" is a symlink,
250 	//   only the link is removed.
251 	//   Returns TRUE on success, FALSE on error.
252 	//
253 
254 	struct stat st;
255 	int result = 0;
256 
257 	if (!cc->removelimit)
258 		return TRUE;
259 	if (lstat(path, &st) < 0) {
260 		fprintf (stderr, "%s: lstat(%s): %s\n", me, path, serror());
261 		cs->errors++;
262 		return FALSE;
263 	}
264 	if ((st.st_mode & S_IFMT) == S_IFDIR) {
265 		if (cc->flags & MIRROR_PRESERVEDIRS) {
266 			if (cc->debug != DEBUG_NONE) {
267 				fprintf (stderr,
268 				    "%s: warning: directory %s:\n", me, path);
269 				fprintf (stderr,
270 				    "will not be removed locally, although it"
271 				    " disappeared from the remote site.\n");
272 			}
273 			//
274 			//   The user set the PRESERVEDIRS flag
275 			//   and wants it this way, so we return
276 			//   "success".
277 			//
278 			return TRUE;
279 		}
280 		iterate_dir (path, (jobfunc *) tonne, allowreport);
281 		if (cc->debug & DEBUG_REPORT)
282 			report (WHY_GONE, -1, st.st_mtime, path);
283 		if (cc->debug & DEBUG_REMOVE_DIRS)
284 			fprintf (stderr, "rmdir %s\n", path);
285 		cs->gonedirs++;
286 		if ((cc->flags & MIRROR_TEST))
287 			result = 0;
288 		else
289 			if ((result = rmdir(path)) < 0) {
290 				fprintf (stderr, "%s: rmdir(%s): %s\n", me,
291 				    (cc->debug & DEBUG_REMOVE_DIRS) ? "" : path,
292 				    serror());
293 				cs->errors++;
294 			}
295 	}
296 	else {
297 		int islink, debuginfo;
298 
299 		if ((islink = (st.st_mode & S_IFMT) == S_IFLNK))
300 			cs->gonelinks++;
301 		else {
302 			cs->gonefiles++;
303 			cs->gonebytes += st.st_size;
304 			cs->growbytes -= st.st_size;
305 		}
306 		debuginfo = cc->debug &
307 		    (islink ? DEBUG_REMOVE_LINKS : DEBUG_REMOVE_FILES);
308 		if (allowreport && cc->debug & DEBUG_REPORT)
309 			report (WHY_GONE, islink ? -2 : st.st_size,
310 			    st.st_mtime, path);
311 		if (debuginfo)
312 			fprintf (stderr, "unlink %s\n", path);
313 		if ((cc->flags & MIRROR_TEST))
314 			result = 0;
315 		else
316 			if ((result = unlink(path)) < 0) {
317 				fprintf (stderr, "%s: unlink(%s): %s\n",
318 				    me, debuginfo ? "" : path, serror());
319 				cs->errors++;
320 			}
321 	}
322 	return result >= 0;
323 }
324 
325 int
mkdir_parents(char * path)326 mkdir_parents (char *path)
327 {
328 	//
329 	//   Creates the parent directories of the given path,
330 	//   if necessary (note: it doesn't try to mkdir path
331 	//   itself).
332 	//
333 
334 	char *parent, *slash;
335 	struct stat st;
336 
337 	if (!(slash = strrchr(path, '/')) || slash == path)
338 		return 0;
339 	if (!(parent = strndup(path, slash - path)))
340 		out_of_memory();
341 
342 	//
343 	// *TODO* We're following local symlinks here.
344 	//        We have to think about whether it makes
345 	//        sense or not (I think it does).
346 	//
347 	if (stat(parent, &st) < 0) {
348 		if (mkdir_parents(parent) < 0) {
349 			free (parent);
350 			return -1;
351 		}
352 		if (cc->debug & DEBUG_REPORT)
353 			report (WHY_NEW, -1, now, parent);
354 		if (cc->debug & DEBUG_CREATE_DIRS)
355 			fprintf (stderr, "mkdir %s\n", parent);
356 		cs->newdirs++;
357 		if (!(cc->flags & MIRROR_TEST) && mkdir(parent, 0700) < 0) {
358 			fprintf (stderr, "%s: mkdir(%s): %s\n", me,
359 			    (cc->debug & DEBUG_CREATE_DIRS) ? "" : parent,
360 			    serror());
361 			cs->errors++;
362 			free (parent);
363 			return -1;
364 		}
365 		if (cc->changegroup) {
366 			if (cc->debug & DEBUG_CHX)
367 				fprintf (stderr, "chown(\"%s\", -1, %d)\n",
368 				    parent, cc->newgroup);
369 			if (!(cc->flags & MIRROR_TEST))
370 				chown (parent, -1, cc->newgroup);
371 		}
372 		if (cc->debug & DEBUG_CHX)
373 			fprintf (stderr, "chmod(\"%s\", 0%o)\n",
374 			    parent, cc->perms_dir);
375 		if (!(cc->flags & MIRROR_TEST))
376 			chmod (parent, cc->perms_dir);
377 	}
378 	free (parent);
379 	return 0;
380 }
381 
382 static int
check_mkdir(omifile * od,char * path,mode_t * oldmode)383 check_mkdir (omifile *od, char *path, mode_t *oldmode)
384 {
385 	//
386 	//   Creates the specified directory, if it does not
387 	//   already exist.  If there is already an entry with
388 	//   that name which is not a directory (and not a
389 	//   symlink to a directory), it is tried to be removed
390 	//   first.
391 	//
392 	//   Return values:
393 	//      -1   error
394 	//       0   success, the directory already exists
395 	//       1   success, dir exists, mtime has to be updated
396 	//       2   success, dir exists, mtime & modes have to be updated
397 	//       3   success, dir did not exist and has been created
398 	//
399 
400 	struct stat st;
401 	mode_t perms;
402 
403 	//
404 	//   *TODO* We are following local symlinks here.
405 	//          This should be an option.
406 	//
407 	if (stat(path, &st) >= 0)
408 		if ((st.st_mode & S_IFMT) == S_IFDIR) {
409 			od->flags |= OFLAG_LEXISTS;
410 			if ((cc->flags & MIRROR_PERMISSIONS)
411 			    && !(od->flags & OFLAG_BADPERM))
412 				perms = od->mode & ACCESSPERMS;
413 			else
414 				perms = cc->perms_dir;
415 			if ((st.st_mode & ALLPERMS) != perms) {
416 				*oldmode = st.st_mode;
417 				return 2;
418 			}
419 			else if (od->mtime &&
420 			    time_diff(st.st_mtime, od->mtime, od) >= 1)
421 				return 1;
422 			else
423 				return 0;
424 		}
425 		else
426 			tonne (path, path);
427 	if (cc->debug & DEBUG_REPORT)
428 		report (WHY_NEW, -1, od->mtime, path);
429 	if (cc->debug & DEBUG_CREATE_DIRS)
430 		fprintf (stderr, "mkdir %s\n", path);
431 	cs->newdirs++;
432 	if (!(cc->flags & MIRROR_TEST) && mkdir(path, 0700) < 0) {
433 		fprintf (stderr, "%s: mkdir(%s): %s\n", me,
434 		    (cc->debug & DEBUG_CREATE_DIRS) ? "" : path,
435 		    serror());
436 		cs->errors++;
437 		return -1;
438 	}
439 	if (cc->changegroup) {
440 		if (cc->debug & DEBUG_CHX)
441 			fprintf (stderr, "chown(\"%s\", -1, %d)\n",
442 			    path, cc->newgroup);
443 		if (!(cc->flags & MIRROR_TEST))
444 			chown (path, -1, cc->newgroup);
445 	}
446 	return 3;
447 }
448 
449 int entries_total;		//  total number of (local) entries
450 simple_list removal_list;	//  list of entries queued for removal
451 
452 static void
queue_tonne(char * path)453 queue_tonne (char *path)
454 {
455 	//
456 	//   Queues a file for removal.
457 	//
458 
459 	simple_list_add_string (&removal_list, path);
460 }
461 
462 static int
perform_tonne(char * path)463 perform_tonne (char *path)
464 {
465 	//
466 	//   Performs all file removals that have been
467 	//   queued, if allowed by the removelimit.
468 	//
469 	//   Returns 1 if at least one file has been
470 	//   removed, otherwise 0.
471 	//
472 
473 	int entries_tonne;
474 	int changes;
475 
476 	changes = 0;
477 	entries_tonne = simple_list_count(&removal_list);
478 	if (entries_total && (cc->removelimit > 99 ||
479 	    ((100 * entries_tonne) / entries_total < cc->removelimit))) {
480 		if ((changes = entries_tonne) > 0)
481 			simple_list_traverse (&removal_list, (jobfunc *) tonne,
482 			    &removal_list, TRAVERSE_IGNORERESULT);
483 	}
484 	else
485 		if (entries_tonne && cc->debug != DEBUG_NONE) {
486 			fprintf (stderr,
487 			    "%s: warning: directory %s:\n", me, path);
488 			fprintf (stderr,
489 			    "Number of disappeared files exceeds removal "
490 			    "limit of %d%%: no files removed.\n",
491 			    cc->removelimit);
492 		}
493 	simple_list_done (&removal_list);
494 	return changes;
495 }
496 
497 static int
match_locexclude(char * path)498 match_locexclude (char *path)
499 {
500 	//
501 	//   Returns MATCH_EXCLUDE or MATCH_INCLUDE.
502 	//
503 
504 	int result;
505 
506 	if (!(result = simple_list_traverse(globalconfig.locexclude,
507 	    (jobfunc *) regex_job, path, 0))) {
508 		if (!(result = simple_list_traverse(ct->conf->locexclude,
509 		    (jobfunc *) regex_job, path, 0))) {
510 			if (!(result = simple_list_traverse(cc->locexclude,
511 			    (jobfunc *) regex_job, path, 0))) {
512 				if (cc->debug & DEBUG_LOCAL_REGEX)
513 					fprintf (stderr, "local  %s: %s\n",
514 					    matchdesc[MATCH_INCLUDE], path);
515 				return MATCH_INCLUDE;
516 			}
517 		}
518 	}
519 	if ((cc->debug & DEBUG_LOCAL_REGEX) && result)
520 		fprintf (stderr, "local  %s: %s\n", matchdesc[result], path);
521 	return result;
522 }
523 
524 static int
check_cleanfile(char * name,omifile * omidir)525 check_cleanfile (char *name, omifile *omidir)
526 {
527 	//
528 	//   Try to locate the local file "name" in the remote
529 	//   directory.  If not found, or if the remote file
530 	//   has an incompatible type (e.g. directory <-> plain
531 	//   file), then remove the local file.
532 	//
533 
534 	struct stat st;
535 	omifile *of;
536 	char *basename;
537 	int i;
538 
539 	if (match_locexclude(name) == MATCH_EXCLUDE)
540 		return 0;
541 
542 	entries_total++;
543 	if ((basename = strrchr(name, '/')))
544 		basename++;
545 	else
546 		basename = name;
547 
548 	//
549 	//   We perform a linear search to locate the name in
550 	//   "omidir".  Note that we cannot assume that omidir
551 	//   is sorted.  It might be more efficient to qsort()
552 	//   omidir first in check_cleandir() and then perform
553 	//   a binary search here, especially if the directory
554 	//   is very large.
555 	//
556 
557 	of = (omifile *) omidir->data;
558 	for (i = 0; i < omidir->size; i++, of++)
559 		if (!strcmp(of->name, basename)
560 		    && !(of->flags & OFLAG_EXCLUDE))
561 			break;
562 	if (i >= omidir->size) {
563 		//
564 		//   If not found in remote dir, remove it.
565 		//
566 		queue_tonne (name);
567 		return 0;
568 	}
569 
570 	//
571 	//   The file exists both locally and on the remote
572 	//   site.  Further handling depends on the type of
573 	//   the files.
574 	//
575 	//   If the remote file is a symlink, we have to mirror
576 	//   it as such, because following remote symlinks is
577 	//   difficult (and normally we don't want to follow
578 	//   remote symlinks anyway).
579 	//
580 	//   *TODO* We are following local symlinks here
581 	//          (except if the remote file is a symlink,
582 	//          too).  This should be an option.
583 	//
584 
585 	if ((of->mode & S_IFMT) == S_IFLNK) {
586 		//
587 		//   If the remote file is a symlink, the local
588 		//   file must be a symlink, too, otherwise
589 		//   remove it.
590 		//
591 		if (lstat(name, &st) < 0) {
592 			//
593 			//   Trouble.  Bad luck.
594 			//   Probably "permission denied".
595 			//
596 			fprintf (stderr, "%s: lstat(%s): %s\n",
597 			    me, name, serror());
598 			cs->errors++;
599 			return -1;
600 		}
601 		if ((st.st_mode & S_IFMT) != S_IFLNK) {
602 			queue_tonne (name);
603 		}
604 		else
605 			of->flags |= OFLAG_LEXISTS;
606 		return 0;
607 	}
608 
609 	//
610 	// *TODO* *CHANGED* lstat() instead of stat():
611 	//                  Do not follow local symlinks
612 	//                  by default!
613 	//
614 	if (lstat(name, &st) < 0) {
615 		//
616 		//   If the stat fails (although we know that
617 		//   the file exists), it's probably a problem
618 		//   with the permissions.  We can't do much
619 		//   about that.
620 		//
621 		fprintf (stderr, "%s: stat(%s): %s\n", me, name, serror());
622 		cs->errors++;
623 		return -1;
624 	}
625 	if ((of->mode & S_IFMT) != (st.st_mode & S_IFMT)) {
626 		//
627 		//   If the remote file is a directory and the
628 		//   local file is a plain file, or vice versa,
629 		//   remove the local file.
630 		//
631 		queue_tonne (name);
632 	}
633 	else
634 		of->flags |= OFLAG_LEXISTS;
635 	return 0;
636 }
637 
638 static int
check_cleandir(char * path,omifile * omidir)639 check_cleandir (char *path, omifile *omidir)
640 {
641 	//
642 	//   Remove entries in the specified local directory
643 	//   which are not present on the remote site, or which
644 	//   have incompatible types.
645 	//
646 	//   Return values:
647 	//      0   no files removed.
648 	//      1   one or more files have been removed.
649 	//
650 
651 	entries_total = 0;
652 	simple_list_init (&removal_list);
653 	iterate_dir (path, (jobfunc *) check_cleanfile, omidir);
654 	return perform_tonne(path);
655 }
656 
657 static int
update_mtime(char * name,omifile * of)658 update_mtime (char *name, omifile *of)
659 {
660 	struct utimbuf times;
661 	int result;
662 
663 	if ((cc->flags & MIRROR_DONTTOUCH) || !of->mtime)
664 		return 0;
665 	times.actime = of->mtime;
666 	times.modtime = of->mtime;
667 	if (cc->debug & DEBUG_CHX)
668 		fprintf (stderr, "utime(\"%s\", [%ld])\n",
669 		    name, (long) times.modtime);
670 	if ((cc->flags & MIRROR_TEST))
671 		result = 0;
672 	else
673 		if ((result = utime(name, &times)) < 0) {
674 			fprintf (stderr, "%s: utime(): %s\n", me, serror());
675 			cs->errors++;
676 		}
677 	return result;
678 }
679 
680 static int
retrieve(ftpstat * fs,char * rname,char * lname,omifile * of)681 retrieve (ftpstat *fs, char *rname, char *lname, omifile *of)
682 {
683 	//
684 	//   Retrieve the remote file "rname" to the local file
685 	//   "lname".  It is a plain file (no directory and no
686 	//   symlink).
687 	//
688 	//   We first create the local file under a temporary
689 	//   name, which we rename to "lname" when the transfer
690 	//   is done successfully.  (This causes the mtime of
691 	//   the local directory to be changed, so we have to
692 	//   reset it after we're done with that directory.)
693 	//
694 	//   Return values:
695 	//      -3   error, stop
696 	//      -2   error, and we did retry
697 	//      -1   error, no retry was done, or rename() failed
698 	//       0   success
699 	//
700 	// *TODO* The error handling definitely needs to be
701 	//        improved!!!
702 	//
703 
704 	char *tmpname, *cptr;
705 	ftpreply *fr;
706 	int result;
707 	mode_t perms;
708 
709 	tmpname = tmalloc(strlen(lname) + 10);
710 	if ((cptr = strrchr(lname, '/')))
711 		strncpy (tmpname, lname, ++cptr - lname);
712 	else
713 		cptr = lname;
714 	tmpname[cptr - lname] = 0;
715 	strcat (tmpname, ".in.");
716 	strcat (tmpname, cptr);
717 	strcat (tmpname, ".omi.");
718 	if (cc->debug & DEBUG_CREATE_FILES)
719 		fprintf (stderr, "creating %s\n", lname);
720 	if (of->flags & OFLAG_LEXISTS) {
721 		cs->updfiles++;
722 		cs->updbytes += of->size;
723 	}
724 	else {
725 		cs->newfiles++;
726 		cs->newbytes += of->size;
727 	}
728 	cs->growbytes += of->size;
729 	if (!(cc->flags & MIRROR_TEST)) {
730 		setsize_progress (of->size);
731 		fr = ftp_retrieve(fs, rname, tmpname);
732 		if (fr->code >= 400) {
733 			free (tmpname);
734 			if (fr->code % 100 != 99) {
735 				fprintf (stderr,
736 				    "%s: error retrieving file %s\n",
737 				    me, rname);
738 				fprintf (stderr,
739 				    "%s: [%d] %s\n", cptr, fr->code, fr->line);
740 			}
741 			cs->errors++;
742 			if (fr->retry == RETRY_NO)
743 				result = -1;
744 			else if (fr->retry == RETRY_STOP)
745 				result = -3;
746 			else
747 				result = -2;
748 			ftp_freereply (fr);
749 			return result;
750 		}
751 		ftp_freereply (fr);
752 	}
753 	if (cc->changegroup) {
754 		if (cc->debug & DEBUG_CHX)
755 			fprintf (stderr, "chown(\"%s\", -1, %d)\n",
756 			    tmpname, cc->newgroup);
757 		if (!(cc->flags & MIRROR_TEST))
758 			chown (tmpname, -1, cc->newgroup);
759 	}
760 	if ((cc->flags & MIRROR_PERMISSIONS) && !(of->flags & OFLAG_BADPERM))
761 		perms = of->mode & ACCESSPERMS;
762 	else
763 		perms = cc->perms_file;
764 	if (cc->debug & DEBUG_CHX)
765 		fprintf (stderr, "chmod(\"%s\", 0%o)\n",
766 		    tmpname, perms);
767 	if (!(cc->flags & MIRROR_TEST))
768 		chmod (tmpname, perms);
769 	update_mtime (tmpname, of);
770 	if (cc->debug & DEBUG_CHX)
771 		fprintf (stderr, "rename(\"%s\", \"%s\")\n", tmpname, lname);
772 	if ((cc->flags & MIRROR_TEST))
773 		result = 0;
774 	else
775 		result = rename(tmpname, lname);
776 	free (tmpname);
777 	return result;
778 }
779 
780 static int
check_symlink(char * name,omifile * of)781 check_symlink (char *name, omifile *of)
782 {
783 	//
784 	//   Check if the remote symlink already exists locally
785 	//   and points to the same target.  If not, create it.
786 	//
787 	//   Return values:
788 	//      < 0   error
789 	//        0   no change
790 	//        1   symlink is new or has changed
791 	//
792 
793 	int namesize;
794 	char linkname[PATH_MAX + 1];
795 	int result;
796 
797 	if ((namesize = readlink(name, linkname, PATH_MAX)) >= 0) {
798 		linkname[namesize] = '\0';
799 		if (!strcmp(linkname, (char *) of->data)) {
800 			//
801 			//   The symlink already exists locally
802 			//   and points to the same target.
803 			//
804 			return 0;
805 		}
806 		else {
807 			//
808 			//   There is a symlink with that name,
809 			//   but it points to a different
810 			//   target, so remove it.
811 			//
812 			if (cc->debug & DEBUG_REPORT)
813 				report (WHY_LINK, -2, of->mtime, name);
814 			tonne (name, NULL);
815 			cs->gonelinks--;
816 			cs->updlinks++;
817 		}
818 	}
819 	else {
820 		cs->newlinks++;
821 		if (cc->debug & DEBUG_REPORT)
822 			report (WHY_NEW, -2, of->mtime, name);
823 	}
824 	if (cc->debug & DEBUG_CREATE_LINKS)
825 		fprintf (stderr, "symlink %s -> %s\n",
826 		    name, (char *) of->data);
827 	if ((cc->flags & MIRROR_TEST))
828 		result = 0;
829 	else
830 		if ((result = symlink((char *) of->data, name)) < 0) {
831 			fprintf (stderr, "%s: symlink(%s): %s\n", me,
832 			    (cc->debug & DEBUG_CREATE_LINKS) ? "" : name,
833 			    serror());
834 			cs->errors++;
835 			return -1;
836 		}
837 	return 1;
838 }
839 
840 static int
check_retrieve(ftpstat * fs,char * rpath,char * lpath,omifile * omidir)841 check_retrieve (ftpstat *fs, char *rpath, char *lpath, omifile *omidir)
842 {
843 	//
844 	//   Retrieve all files from "omidir" that are not
845 	//   present locally, or that have changed.  Also
846 	//   recurse into subdirectories as necessary.
847 	//
848 	//   Return values:
849 	//      < 0   error
850 	//        0   nothing changed
851 	//        1   at least one new or updated file
852 	//
853 
854 	omifile *of;
855 	char *lname, *rname;
856 	struct stat st;
857 	int i, result, changed;
858 	mode_t perms;
859 
860 	changed = 0;
861 	of = (omifile *) omidir->data;
862 	for (i = 0; i < omidir->size; i++, of++) {
863 		if (of->flags & OFLAG_EXCLUDE)
864 			continue;
865 		//
866 		// *TODO* This lname/rname stuff could be
867 		//        optimized like in iterate_dir().
868 		//
869 		lname = tmalloc(strlen(lpath) + strlen(of->name) + 2);
870 		strcpy (lname, lpath);
871 		strcat (lname, "/");
872 		strcat (lname, of->name);
873 		rname = tmalloc(strlen(rpath) + strlen(of->name) + 2);
874 		strcpy (rname, rpath);
875 		strcat (rname, "/");
876 		strcat (rname, of->name);
877 		if ((of->mode & S_IFMT) == S_IFDIR) {
878 			//
879 			//   If the remote file has type
880 			//   "directory", recurse into it.
881 			//   It doesn't matter whether it
882 			//   exists locally or not, and which
883 			//   type it has, since mirror_dir()
884 			//   cares for that.
885 			//
886 			result = mirror_dir(fs, rname, lname, of);
887 			if (result < -1) {
888 				free (lname);
889 				free (rname);
890 				return result;
891 			}
892 			if (result >= 1)
893 				changed = 1;
894 		}
895 		else if ((of->mode & S_IFMT) == S_IFLNK) {
896 			//
897 			//   If the remote file has type
898 			//   "symlink", duplicate it locally
899 			//   if it doesn't already exist and
900 			//   point to the same target.
901 			//
902 			cs->totallinks++;
903 			if (check_symlink(lname, of) >= 1)
904 				changed = 1;
905 		}
906 		else {
907 		    //
908 		    //   The remote file is a plain file.
909 		    //
910 		    cs->totalfiles++;
911 		    cs->totalbytes += of->size;
912 		    if (stat(lname, &st) < 0) {
913 			//
914 			//   The remote file is a plain file.
915 			//   Since it doesn't exist locally,
916 			//   get it.
917 			//
918 			if (cc->debug & DEBUG_REASONING)
919 				fprintf (stderr, "new file %s\n",
920 				    lname);
921 			if (cc->debug & DEBUG_REPORT)
922 				report (WHY_NEW, of->size, of->mtime, lname);
923 			changed = 1;
924 			result = retrieve(fs, rname, lname, of);
925 			if (result < -1) {
926 				free (lname);
927 				free (rname);
928 				return result;
929 			}
930 		    }
931 		    else if (st.st_size != of->size) {
932 			//
933 			//   It's a plain file that is both
934 			//   present on the local and remote
935 			//   site, but with different sizes.
936 			//   Get the file from the remote site.
937 			//   Note that we do not have to remove
938 			//   the local file, as it will be
939 			//   overwritten when the retrieval is
940 			//   done.
941 			//
942 			// *TODO* In a later version, it might
943 			//        be useful to try a "reget" if
944 			//        the local file is smaller
945 			//        than the remote file, but
946 			//        with the same mtime.  This
947 			//        could at least be an option.
948 			//        Of course we have to check
949 			//        first that the remote server
950 			//        supports the REST command
951 			//        appropriately.
952 			//
953 			if (cc->debug & DEBUG_REASONING)
954 				fprintf (stderr, "size mismatch for %s\n",
955 				    lname);
956 			if (cc->debug & DEBUG_REPORT)
957 				report (WHY_SIZE,
958 				    st.st_size, of->size, lname);
959 			cs->growbytes -= st.st_size;
960 			//
961 			//   We have to set `changed' to 1 in
962 			//   this case, too, because retrieve()
963 			//   changes the mtime of the local
964 			//   directory!
965 			//
966 			changed = 1;
967 			result = retrieve(fs, rname, lname, of);
968 			if (result < -1) {
969 				free (lname);
970 				free (rname);
971 				return result;
972 			}
973 		    }
974 		    else if ((result =
975 		      time_diff(st.st_mtime, of->mtime, of)) > 1) {
976 			//
977 			//   The size is the same, but the
978 			//   mtimes differ.  Get the file.
979 			//
980 			if (cc->debug & DEBUG_REASONING)
981 				fprintf (stderr, "mtime mismatch for %s\n",
982 				    lname);
983 			if (cc->debug & DEBUG_REPORT)
984 				report (WHY_TIME,
985 				    st.st_mtime, of->mtime, lname);
986 			cs->growbytes -= st.st_size;
987 			changed = 1;	//   Se above!
988 			result = retrieve(fs, rname, lname, of);
989 			if (result < -1) {
990 				free (lname);
991 				free (rname);
992 				return result;
993 			}
994 		    }
995 		    else {
996 			if ((cc->flags & MIRROR_PERMISSIONS)
997 			    && !(of->flags & OFLAG_BADPERM))
998 				perms = of->mode & ACCESSPERMS;
999 			else
1000 				perms = cc->perms_file;
1001 			if ((st.st_mode & ALLPERMS) != perms) {
1002 				cs->permfiles++;
1003 				if (cc->debug & DEBUG_REPORT)
1004 					report (WHY_MODE,
1005 					    st.st_mode, perms, lname);
1006 				if (cc->debug & DEBUG_CHX)
1007 					fprintf (stderr, "chmod(\"%s\", 0%o)\n",
1008 					    lname, perms);
1009 				if (!(cc->flags & MIRROR_TEST))
1010 					chmod (lname, perms);
1011 			}
1012 			if (result == 1)	// result from time_diff()
1013 				update_mtime (lname, of);
1014 		    }
1015 		}
1016 		free (rname);
1017 		free (lname);
1018 	}
1019 	return changed;
1020 }
1021 
1022 typedef int sortjob(const void *, const void *);
1023 
1024 static int
timesortjob(const omifile * a,const omifile * b)1025 timesortjob (const omifile *a, const omifile *b)
1026 {
1027 	//
1028 	//   Sort two files according to the timestamp,
1029 	//   so that the newer one comes first.
1030 	//
1031 	//   If there are files with the EXCLUDE flag,
1032 	//   they should come last.
1033 	//
1034 
1035 	bool excla, exclb;
1036 
1037 	excla = (a->flags & OFLAG_EXCLUDE) != 0;
1038 	exclb = (b->flags & OFLAG_EXCLUDE) != 0;
1039 	if (excla != exclb)
1040 		if (excla)
1041 			return 1;
1042 		else
1043 			return -1;
1044 
1045 	if (a->mtime > b->mtime)
1046 		return -1;
1047 	if (a->mtime < b->mtime)
1048 		return 1;
1049 	return 0;
1050 }
1051 
1052 static void
filter_dir(omifile * od)1053 filter_dir (omifile *od)
1054 {
1055 	omifile *of;
1056 	int i;
1057 
1058 	if (cc->newest && od->size > cc->newest) {
1059 		//
1060 		//   First sort the directory entries by timestamps,
1061 		//   newest first.
1062 		//
1063 
1064 		qsort (od->data, od->size, sizeof(omifile),
1065 		    (sortjob *) timesortjob);
1066 
1067 		//
1068 		//   Now we only need the first cc->newest entries,
1069 		//   so we discard all the rest.
1070 		//
1071 
1072 		of = (omifile *) od->data + cc->newest;
1073 		for (i = cc->newest; i < od->size; i++, of++) {
1074 			if (of->name)
1075 				free (of->name);
1076 			switch (of->mode & S_IFMT) {
1077 			case S_IFDIR:
1078 				free_tree (of);
1079 				break;
1080 			case S_IFLNK:
1081 				if (of->data)
1082 					free (of->data);
1083 			}
1084 		}
1085 		od->size = cc->newest;
1086 		if (!(od->data = realloc(od->data, od->size * sizeof(omifile))))
1087 			out_of_memory();
1088 	}
1089 }
1090 
1091 int
mirror_dir(ftpstat * fs,char * rpath,char * lpath,omifile * omidir)1092 mirror_dir (ftpstat *fs, char *rpath, char *lpath, omifile *omidir)
1093 {
1094 	//
1095 	//   Mirror the remote directory "rpath" (the tree
1096 	//   structure is given in "omidir") to the local
1097 	//   directory "lpath".
1098 	//
1099 	//   Return values
1100 	//      < 0   error
1101 	//        0   the directory already existed locally
1102 	//        1   it's a new directory
1103 	//
1104 
1105 	int retrresult, cleanresult, makeresult;
1106 	mode_t perms, oldmode;
1107 
1108 	cs->totaldirs++;
1109 
1110 	//
1111 	//   Filter entries in this directory, if necessary.
1112 	//
1113 
1114 	filter_dir (omidir);
1115 
1116 	//
1117 	//   First, create the local directory if it doesn't
1118 	//   already exist.
1119 	//
1120 
1121 	if ((makeresult = check_mkdir(omidir, lpath, &oldmode)) < 0) {
1122 		fprintf (stderr, "%s: Can't create local directory, "
1123 		    "skipping directory tree.\n", me);
1124 		return -1;
1125 	}
1126 
1127 	//
1128 	//   Second, iterate the local directory, compare
1129 	//   each entry with the "omidir", and remove local
1130 	//   files not present on the remote server.
1131 	//
1132 
1133 	if ((cleanresult = check_cleandir(lpath, omidir)) < 0)
1134 		return cleanresult;
1135 
1136 	//
1137 	//   Third, iterate the "omidir", compare every entry
1138 	//   with the local file (if present), and download it
1139 	//   if necessary.  Also recurse into subdirectories.
1140 	//
1141 
1142 	if ((retrresult = check_retrieve(fs, rpath, lpath, omidir)) < 0)
1143 		return retrresult;
1144 
1145 	if ((omidir->flags & OFLAG_LEXISTS) && (cleanresult || retrresult))
1146 		cs->upddirs++;
1147 
1148 	//
1149 	//   Finally, update permission modes and time stamps
1150 	//   of the local directory if anything has changed
1151 	//   inside, or if the local directory's timestamp is
1152 	//   different from the remote one's.
1153 	//
1154 
1155 	if (makeresult >= 2) {
1156 		if ((cc->flags & MIRROR_PERMISSIONS)
1157 		    && !(omidir->flags & OFLAG_BADPERM))
1158 			perms = omidir->mode & ACCESSPERMS;
1159 		else
1160 			perms = cc->perms_dir;
1161 		if (makeresult < 3) {
1162 			cs->permdirs++;
1163 			if (cc->debug & DEBUG_REPORT)
1164 				report (WHY_MODE,
1165 				    oldmode, perms | S_IFDIR, lpath);
1166 		}
1167 		if (cc->debug & DEBUG_CHX)
1168 			fprintf (stderr, "chmod(\"%s\", 0%o)\n",
1169 			    lpath, perms);
1170 		if (!(cc->flags & MIRROR_TEST))
1171 			chmod (lpath, perms);
1172 	}
1173 	if (makeresult || cleanresult || retrresult)
1174 		update_mtime (lpath, omidir);
1175 	return makeresult >= 3;
1176 }
1177 
1178 //--
1179