Separate the MD5 code into its own module.
[dragonfly.git] / bin / cpdup / cpdup.c
1 /*-
2  * CPDUP.C
3  *
4  * CPDUP <options> source destination
5  *
6  * (c) Copyright 1997-1999 by Matthew Dillon and Dima Ruban.  Permission to
7  *     use and distribute based on the FreeBSD copyright.  Supplied as-is,
8  *     USE WITH EXTREME CAUTION.
9  *
10  * This program attempts to duplicate the source onto the destination as 
11  * exactly as possible, retaining modify times, flags, perms, uid, and gid.
12  * It can duplicate devices, files (including hardlinks), softlinks, 
13  * directories, and so forth.  It is recursive by default!  The duplication
14  * is inclusive of removal of files/directories on the destination that do
15  * not exist on the source.  This program supports a per-directory exception
16  * file called .cpignore, or a user-specified exception file.
17  *
18  * Safety features:
19  *
20  *      - does not cross partition boundries on source
21  *      - asks for confirmation on deletions unless -i0 is specified
22  *      - refuses to replace a destination directory with a source file
23  *        unless -s0 is specified.
24  *      - terminates on error
25  *
26  * Copying features:
27  *
28  *      - does not copy file if mtime, flags, perms, and size match unless
29  *        forced
30  *
31  *      - copies to temporary and renames-over the original, allowing
32  *        you to update live systems
33  *
34  *      - copies uid, gid, mtime, perms, flags, softlinks, devices, hardlinks,
35  *        and recurses through directories.
36  *
37  *      - accesses a per-directory exclusion file, .cpignore, containing 
38  *        standard wildcarded ( ? / * style, NOT regex) exclusions.
39  *
40  *      - tries to play permissions and flags smart in regards to overwriting 
41  *        schg files and doing related stuff.
42  *
43  *      - Can do MD5 consistancy checks
44  *
45  * $DragonFly: src/bin/cpdup/cpdup.c,v 1.10 2006/04/25 21:30:45 dillon Exp $
46  */
47
48 /*-
49  * Example: cc -O cpdup.c -o cpdup -lmd
50  *
51  * ".MD5.CHECKSUMS" contains md5 checksumms for the current directory.
52  * This file is stored on the source.
53  */
54
55 #include "cpdup.h"
56
57 #define HSIZE   16384
58 #define HMASK   (HSIZE-1)
59 #define HASHF 16
60
61 typedef struct Node {
62     struct Node *no_Next;
63     struct Node *no_HNext;
64     int  no_Value;
65     char no_Name[4];
66 } Node;
67
68 typedef struct List {
69     Node        li_Node;
70     Node        *li_Hash[HSIZE];
71 } List;
72
73 struct hlink {
74     ino_t ino;
75     ino_t dino;
76     char name[2048];
77     struct hlink *next;
78     struct hlink *prev;
79     nlink_t nlinked;
80 };
81
82 struct hlink *hltable[HASHF];
83
84 void RemoveRecur(const char *dpath, dev_t devNo);
85 void InitList(List *list);
86 void ResetList(List *list);
87 int AddList(List *list, const char *name, int n);
88 static struct hlink *hltlookup(struct stat *);
89 static struct hlink *hltadd(struct stat *, const char *);
90 static int shash(const char *s);
91 static void hltdelete(struct hlink *);
92 int YesNo(const char *path);
93 static int xrename(const char *src, const char *dst, u_long flags);
94 static int xlink(const char *src, const char *dst, u_long flags);
95 int WildCmp(const char *s1, const char *s2);
96 int DoCopy(const char *spath, const char *dpath, dev_t sdevNo, dev_t ddevNo);
97
98 int AskConfirmation = 1;
99 int SafetyOpt = 1;
100 int ForceOpt;
101 int VerboseOpt;
102 int QuietOpt;
103 int NoRemoveOpt;
104 int UseMD5Opt;
105 int UseFSMIDOpt;
106 int SummaryOpt;
107 int EnableDirectoryRetries;
108 const char *UseCpFile;
109 const char *MD5CacheFile;
110 const char *FSMIDCacheFile;
111
112 int64_t CountSourceBytes;
113 int64_t CountSourceItems;
114 int64_t CountCopiedItems;
115 int64_t CountReadBytes;
116 int64_t CountWriteBytes;
117 int64_t CountRemovedItems;
118
119 int
120 main(int ac, char **av)
121 {
122     int i;
123     char *src = NULL;
124     char *dst = NULL;
125     struct timeval start;
126
127     gettimeofday(&start, NULL);
128     for (i = 1; i < ac; ++i) {
129         char *ptr = av[i];
130         int v = 1;
131
132         if (*ptr != '-') { 
133             if (src == NULL) {
134                 src = ptr;
135             } else if (dst == NULL) {
136                 dst = ptr;
137             } else {
138                 fatal("too many arguments");
139                 /* not reached */
140             }
141             continue;
142         }
143         ptr += 2;
144
145         if (*ptr)
146             v = strtol(ptr, NULL, 0);
147
148         switch(ptr[-1]) {
149         case 'v':
150             VerboseOpt = 1;
151             while (*ptr == 'v') {
152                 ++VerboseOpt;
153                 ++ptr;
154             }
155             if (*ptr >= '0' && *ptr <= '9')
156                 VerboseOpt = strtol(ptr, NULL, 0);
157             break;
158         case 'I':
159             SummaryOpt = v;
160             break;
161         case 'o':
162             NoRemoveOpt = v;
163             break;
164         case 'x':
165             UseCpFile = ".cpignore";
166             break;
167         case 'X':
168             UseCpFile = (*ptr) ? ptr : av[++i];
169             break;
170         case 'f':
171             ForceOpt = v;
172             break;
173         case 'i':
174             AskConfirmation = v;
175             break;
176         case 's':
177             SafetyOpt = v;
178             break;
179         case 'q':
180             QuietOpt = v;
181             break;
182         case 'k':
183             UseFSMIDOpt = v;
184             FSMIDCacheFile = ".FSMID.CHECK";
185             break;
186         case 'K':
187             UseFSMIDOpt = v;
188             FSMIDCacheFile = av[++i];
189             break;
190         case 'M':
191             UseMD5Opt = v;
192             MD5CacheFile = av[++i];
193             break;
194         case 'm':
195             UseMD5Opt = v;
196             MD5CacheFile = ".MD5.CHECKSUMS";
197             break;
198         case 'u':
199             setvbuf(stdout, NULL, _IOLBF, 0);
200             break;
201         default:
202             fatal("illegal option: %s\n", ptr - 2);
203             /* not reached */
204             break;
205         }
206     }
207
208     /*
209      * dst may be NULL only if -m option is specified,
210      * which forces an update of the MD5 checksums
211      */
212
213     if (dst == NULL && UseMD5Opt == 0) {
214         fatal(NULL);
215         /* not reached */
216     }
217     if (dst) {
218         i = DoCopy(src, dst, (dev_t)-1, (dev_t)-1);
219     } else {
220         i = DoCopy(src, NULL, (dev_t)-1, (dev_t)-1);
221     }
222     md5_flush();
223     fsmid_flush();
224
225     if (SummaryOpt && i == 0) {
226         long duration;
227         struct timeval end;
228
229         gettimeofday(&end, NULL);
230         CountSourceBytes += sizeof(struct stat) * CountSourceItems;
231         CountReadBytes += sizeof(struct stat) * CountSourceItems;
232         CountWriteBytes +=  sizeof(struct stat) * CountCopiedItems;
233         CountWriteBytes +=  sizeof(struct stat) * CountRemovedItems;
234
235         duration = end.tv_sec - start.tv_sec;
236         duration *= 1000000;
237         duration += end.tv_usec - start.tv_usec;
238         if (duration == 0) duration = 1;
239         logstd("cpdup completed successfully\n");
240         logstd("%lld bytes source %lld bytes read %lld bytes written (%.1fX speedup)\n",
241             (long long)CountSourceBytes,
242             (long long)CountReadBytes,
243             (long long)CountWriteBytes,
244             ((double)CountSourceBytes * 2.0) / ((double)(CountReadBytes + CountWriteBytes)));
245         logstd("%lld source items %lld items copied %lld things deleted\n",
246             (long long)CountSourceItems,
247             (long long)CountCopiedItems,
248             (long long)CountRemovedItems);
249         logstd("%.1f seconds %5d Kbytes/sec synced %5d Kbytes/sec scanned\n",
250             (float)duration / (float)1000000,
251             (long)((long)1000000 * (CountReadBytes + CountWriteBytes) / duration  / 1024.0),
252             (long)((long)1000000 * CountSourceBytes / duration / 1024.0));
253     }
254     exit((i == 0) ? 0 : 1);
255 }
256
257 static struct hlink *
258 hltlookup(struct stat *stp)
259 {
260     struct hlink *hl;
261     int n;
262
263     n = stp->st_ino % HASHF;
264
265     for (hl = hltable[n]; hl; hl = hl->next)
266         if (hl->ino == stp->st_ino)
267               return hl;
268
269     return NULL;
270 }
271
272 static struct hlink *
273 hltadd(struct stat *stp, const char *path)
274 {
275     struct hlink *new;
276     int n;
277
278     if (!(new = malloc(sizeof (struct hlink)))) {
279         fprintf(stderr, "out of memory\n");
280         exit(EXIT_FAILURE);
281     }
282
283     /* initialize and link the new element into the table */
284     new->ino = stp->st_ino;
285     new->dino = 0;
286     strncpy(new->name, path, 2048);
287     new->nlinked = 1;
288     new->prev = NULL;
289     n = stp->st_ino % HASHF;
290     new->next = hltable[n];
291     if (hltable[n])
292         hltable[n]->prev = new;
293     hltable[n] = new;
294
295     return new;
296 }
297
298 static void
299 hltdelete(struct hlink *hl)
300 {
301     if (hl->prev) {
302         if (hl->next)
303             hl->next->prev = hl->prev;
304         hl->prev->next = hl->next;
305     } else {
306         if (hl->next)
307             hl->next->prev = NULL;
308
309         hltable[hl->ino % HASHF] = hl->next;
310     }
311
312     free(hl);
313 }
314
315 int
316 DoCopy(const char *spath, const char *dpath, dev_t sdevNo, dev_t ddevNo)
317 {
318     struct stat st1;
319     struct stat st2;
320     int r, mres, fres, st2Valid;
321     struct hlink *hln;
322     List list;
323     u_int64_t size;
324
325     InitList(&list);
326     r = mres = fres = st2Valid = 0;
327     size = 0;
328     hln = NULL;
329
330     if (lstat(spath, &st1) != 0)
331         return(0);
332     st2.st_mode = 0;    /* in case lstat fails */
333     st2.st_flags = 0;   /* in case lstat fails */
334     if (dpath && lstat(dpath, &st2) == 0)
335         st2Valid = 1;
336
337     if (S_ISREG(st1.st_mode)) {
338         size = st1.st_blocks * 512;
339         if (st1.st_size % 512) 
340             size += st1.st_size % 512 - 512;
341     }
342
343     /*
344      * Handle hardlinks
345      */
346
347     if (S_ISREG(st1.st_mode) && st1.st_nlink > 1 && dpath) {
348         if ((hln = hltlookup(&st1)) != NULL) {
349             hln->nlinked++;
350
351             if (st2Valid) {
352                 if (st2.st_ino == hln->dino) {
353                     /*
354                      * hard link is already correct, nothing to do
355                      */
356                     if (VerboseOpt >= 3)
357                         logstd("%-32s nochange\n", (dpath) ? dpath : spath);
358                     if (hln->nlinked == st1.st_nlink)
359                         hltdelete(hln);
360                     CountSourceItems++;
361                     return 0;
362                 } else {
363                     /*
364                      * hard link is not correct, attempt to unlink it
365                      */
366                     if (unlink(dpath) < 0) {
367                         logerr("%-32s hardlink: unable to unlink: %s\n", 
368                             ((dpath) ? dpath : spath), strerror(errno));
369                         hltdelete(hln);
370                         return (r + 1);
371                     }
372                 }
373             }
374
375             if (xlink(hln->name, dpath, st1.st_flags) < 0) {
376                 logerr("%-32s hardlink: unable to link to %s: %s\n",
377                     (dpath ? dpath : spath), hln->name, strerror(errno)
378                 );
379                 hltdelete(hln);
380                 hln = NULL;
381                 ++r;
382             } else {
383                 if (hln->nlinked == st1.st_nlink) {
384                     hltdelete(hln);
385                     hln = NULL;
386                 }
387                 if (r == 0) {
388                     if (VerboseOpt) {
389                         logstd("%-32s hardlink: %s\n", 
390                             (dpath ? dpath : spath),
391                             (st2Valid ? "relinked" : "linked")
392                         );
393                     }
394                     CountSourceItems++;
395                     CountCopiedItems++;
396                     return 0;
397                 }
398             }
399         } else {
400             /*
401              * first instance of hardlink must be copied normally
402              */
403             hln = hltadd(&st1, dpath);
404         }
405     }
406
407     /*
408      * Do we need to copy the file/dir/link/whatever?  Early termination
409      * if we do not.  Always redo links.  Directories are always traversed
410      * except when the FSMID options are used.
411      *
412      * NOTE: st2Valid is true only if dpath != NULL *and* dpath stats good.
413      */
414
415     if (
416         st2Valid &&
417         st1.st_mode == st2.st_mode &&
418         st1.st_flags == st2.st_flags
419     ) {
420         if (S_ISLNK(st1.st_mode) || S_ISDIR(st1.st_mode)) {
421             /*
422              * If FSMID tracking is turned on we can avoid recursing through
423              * an entire directory subtree if the FSMID matches.
424              */
425 #ifdef _ST_FSMID_PRESENT_
426             if (ForceOpt == 0 &&
427                 (UseFSMIDOpt && (fres = fsmid_check(st1.st_fsmid, dpath)) == 0)
428             ) {
429                 if (VerboseOpt >= 3) {
430                     if (UseFSMIDOpt)
431                         logstd("%-32s fsmid-nochange\n", (dpath ? dpath : spath));
432                     else
433                         logstd("%-32s nochange\n", (dpath ? dpath : spath));
434                 }
435                 return(0);
436             }
437 #endif
438         } else {
439             if (ForceOpt == 0 &&
440                 st1.st_size == st2.st_size &&
441                 st1.st_uid == st2.st_uid &&
442                 st1.st_gid == st2.st_gid &&
443                 st1.st_mtime == st2.st_mtime
444                 && (UseMD5Opt == 0 || (mres = md5_check(spath, dpath)) == 0)
445 #ifdef _ST_FSMID_PRESENT_
446                 && (UseFSMIDOpt == 0 || (fres = fsmid_check(st1.st_fsmid, dpath)) == 0)
447 #endif
448             ) {
449                 if (hln)
450                     hln->dino = st2.st_ino;
451                 if (VerboseOpt >= 3) {
452                     if (UseMD5Opt)
453                         logstd("%-32s md5-nochange\n", (dpath ? dpath : spath));
454                     else if (UseFSMIDOpt)
455                         logstd("%-32s fsmid-nochange\n", (dpath ? dpath : spath));
456                     else
457                         logstd("%-32s nochange\n", (dpath ? dpath : spath));
458                 }
459                 CountSourceBytes += size;
460                 CountSourceItems++;
461
462                 return(0);
463             }
464         }
465     }
466     if (st2Valid && !S_ISDIR(st1.st_mode) && S_ISDIR(st2.st_mode)) {
467         if (SafetyOpt) {
468             logerr("%-32s SAFETY - refusing to copy file over directory\n",
469                 (dpath ? dpath : spath)
470             );
471             ++r;                /* XXX */
472             return(0);  /* continue with the cpdup anyway */
473         }
474         if (QuietOpt == 0 || AskConfirmation) {
475             logstd("%-32s WARNING: non-directory source will blow away\n"
476                    "%-32s preexisting dest directory, continuing anyway!\n",
477                    ((dpath) ? dpath : spath), "");
478         }
479         if (dpath)
480             RemoveRecur(dpath, ddevNo);
481     }
482
483     /*
484      * The various comparisons failed, copy it.
485      */
486     if (S_ISDIR(st1.st_mode)) {
487         DIR *dir;
488
489         if (fres < 0)
490             logerr("%-32s/ fsmid-CHECK-FAILED\n", (dpath) ? dpath : spath);
491         if ((dir = opendir(spath)) != NULL) {
492             struct dirent *den;
493             int noLoop = 0;
494
495             if (dpath) {
496                 if (S_ISDIR(st2.st_mode) == 0) {
497                     remove(dpath);
498                     if (mkdir(dpath, st1.st_mode | 0700) != 0) {
499                         logerr("%s: mkdir failed: %s\n", 
500                             (dpath ? dpath : spath), strerror(errno));
501                         r = 1;
502                         noLoop = 1;
503                     }
504                     /*
505                      * Matt: why don't you check error codes here?
506                      */
507                     lstat(dpath, &st2);
508                     chown(dpath, st1.st_uid, st1.st_gid);
509                     CountCopiedItems++;
510                 } else {
511                     /*
512                      * Directory must be scanable by root for cpdup to
513                      * work.  We'll fix it later if the directory isn't
514                      * supposed to be readable ( which is why we fixup
515                      * st2.st_mode to match what we did ).
516                      */
517                     if ((st2.st_mode & 0700) != 0700) {
518                         chmod(dpath, st2.st_mode | 0700);
519                         st2.st_mode |= 0700;
520                     }
521                     if (VerboseOpt >= 2)
522                         logstd("%s\n", dpath ? dpath : spath);
523                 }
524             }
525
526             if ((int)sdevNo >= 0 && st1.st_dev != sdevNo) {
527                 noLoop = 1;
528             } else {
529                 sdevNo = st1.st_dev;
530             }
531
532             if ((int)ddevNo >= 0 && st2.st_dev != ddevNo) {
533                 noLoop = 1;
534             } else {
535                 ddevNo = st2.st_dev;
536             }
537
538             /*
539              * scan .cpignore file for files/directories 
540              * to ignore.
541              */
542
543             if (UseCpFile) {
544                 FILE *fi;
545                 char buf[8192];
546                 char *fpath;
547
548                 if (UseCpFile[0] == '/') {
549                     fpath = mprintf("%s", UseCpFile);
550                 } else {
551                     fpath = mprintf("%s/%s", spath, UseCpFile);
552                 }
553                 AddList(&list, strrchr(fpath, '/') + 1, 1);
554                 if ((fi = fopen(fpath, "r")) != NULL) {
555                     while (fgets(buf, sizeof(buf), fi) != NULL) {
556                         int l = strlen(buf);
557                         CountReadBytes += l;
558                         if (l && buf[l-1] == '\n')
559                             buf[--l] = 0;
560                         if (buf[0] && buf[0] != '#')
561                             AddList(&list, buf, 1);
562                     }
563                     fclose(fi);
564                 }
565                 free(fpath);
566             }
567
568             /*
569              * Automatically exclude MD5CacheFile that we create on the
570              * source from the copy to the destination.
571              *
572              * Automatically exclude a FSMIDCacheFile on the source that
573              * would otherwise overwrite the one we maintain on the target.
574              */
575             if (UseMD5Opt)
576                 AddList(&list, MD5CacheFile, 1);
577             if (UseFSMIDOpt)
578                 AddList(&list, FSMIDCacheFile, 1);
579
580             while (noLoop == 0 && (den = readdir(dir)) != NULL) {
581                 /*
582                  * ignore . and ..
583                  */
584                 char *nspath;
585                 char *ndpath = NULL;
586
587                 if (strcmp(den->d_name, ".") == 0 ||
588                     strcmp(den->d_name, "..") == 0
589                 ) {
590                     continue;
591                 }
592                 /*
593                  * ignore if on .cpignore list
594                  */
595                 if (AddList(&list, den->d_name, 0) == 1) {
596                     continue;
597                 }
598                 nspath = mprintf("%s/%s", spath, den->d_name);
599                 if (dpath)
600                     ndpath = mprintf("%s/%s", dpath, den->d_name);
601                 r += DoCopy(
602                     nspath,
603                     ndpath,
604                     sdevNo,
605                     ddevNo
606                 );
607                 free(nspath);
608                 if (ndpath)
609                     free(ndpath);
610             }
611
612             closedir(dir);
613
614             /*
615              * Remove files/directories from destination that do not appear
616              * in the source.
617              */
618             if (dpath && (dir = opendir(dpath)) != NULL) {
619                 while (noLoop == 0 && (den = readdir(dir)) != NULL) {
620                     /*
621                      * ignore . or ..
622                      */
623                     if (strcmp(den->d_name, ".") == 0 ||
624                         strcmp(den->d_name, "..") == 0
625                     ) {
626                         continue;
627                     }
628                     /*
629                      * If object does not exist in source or .cpignore
630                      * then recursively remove it.
631                      */
632                     if (AddList(&list, den->d_name, 3) == 3) {
633                         char *ndpath;
634
635                         ndpath = mprintf("%s/%s", dpath, den->d_name);
636                         RemoveRecur(ndpath, ddevNo);
637                         free(ndpath);
638                     }
639                 }
640                 closedir(dir);
641             }
642
643             if (dpath) {
644                 if (ForceOpt ||
645                     st2Valid == 0 || 
646                     st1.st_uid != st2.st_uid ||
647                     st1.st_gid != st2.st_gid
648                 ) {
649                     chown(dpath, st1.st_uid, st1.st_gid);
650                 }
651                 if (st2Valid == 0 || st1.st_mode != st2.st_mode) {
652                     chmod(dpath, st1.st_mode);
653                 }
654                 if (st2Valid == 0 || st1.st_flags != st2.st_flags) {
655                     chflags(dpath, st1.st_flags);
656                 }
657             }
658         }
659     } else if (dpath == NULL) {
660         /*
661          * If dpath is NULL, we are just updating the MD5
662          */
663         if (UseMD5Opt && S_ISREG(st1.st_mode)) {
664             mres = md5_check(spath, NULL);
665
666             if (VerboseOpt > 1) {
667                 if (mres < 0)
668                     logstd("%-32s md5-update\n", (dpath) ? dpath : spath);
669                 else
670                     logstd("%-32s md5-ok\n", (dpath) ? dpath : spath);
671             } else if (!QuietOpt && mres < 0) {
672                 logstd("%-32s md5-update\n", (dpath) ? dpath : spath);
673             }
674         }
675     } else if (S_ISREG(st1.st_mode)) {
676         char *path;
677         int fd1;
678         int fd2;
679
680         path = mprintf("%s.tmp", dpath);
681
682         /*
683          * Handle check failure message.
684          */
685         if (mres < 0)
686             logerr("%-32s md5-CHECK-FAILED\n", (dpath) ? dpath : spath);
687         else if (fres < 0)
688             logerr("%-32s fsmid-CHECK-FAILED\n", (dpath) ? dpath : spath);
689
690         if ((fd1 = open(spath, O_RDONLY)) >= 0) {
691             if ((fd2 = open(path, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0) {
692                 /*
693                  * There could be a .tmp file from a previously interrupted
694                  * run, delete and retry.  Fail if we still can't get at it.
695                  */
696                 chflags(path, 0);
697                 remove(path);
698                 fd2 = open(path, O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, 0600);
699             }
700             if (fd2 >= 0) {
701                 /*
702                  * Matt: I think 64k would be faster here
703                  */
704                 char buf[32768];
705                 const char *op;
706                 int n;
707
708                 /*
709                  * Matt: What about holes?
710                  */
711                 op = "read";
712                 while ((n = read(fd1, buf, sizeof(buf))) > 0) {
713                     op = "write";
714                     if (write(fd2, buf, n) != n)
715                         break;
716                     op = "read";
717                 }
718                 close(fd2);
719                 if (n == 0) {
720                     struct timeval tv[2];
721
722                     bzero(tv, sizeof(tv));
723                     tv[0].tv_sec = st1.st_mtime;
724                     tv[1].tv_sec = st1.st_mtime;
725
726                     utimes(path, tv);
727                     chown(path, st1.st_uid, st1.st_gid);
728                     chmod(path, st1.st_mode);
729                     if (xrename(path, dpath, st2.st_flags) != 0) {
730                         logerr("%-32s rename-after-copy failed: %s\n",
731                             (dpath ? dpath : spath), strerror(errno)
732                         );
733                         ++r;
734                     } else {
735                         if (VerboseOpt)
736                             logstd("%-32s copy-ok\n", (dpath ? dpath : spath));
737                         if (st1.st_flags)
738                             chflags(dpath, st1.st_flags);
739                     }
740                     CountReadBytes += size;
741                     CountWriteBytes += size;
742                     CountSourceBytes += size;
743                     CountSourceItems++;
744                     CountCopiedItems++;
745                 } else {
746                     logerr("%-32s %s failed: %s\n",
747                         (dpath ? dpath : spath), op, strerror(errno)
748                     );
749                     remove(path);
750                     ++r;
751                 }
752             } else {
753                 logerr("%-32s create (uid %d, euid %d) failed: %s\n",
754                     (dpath ? dpath : spath), getuid(), geteuid(),
755                     strerror(errno)
756                 );
757                 ++r;
758             }
759             close(fd1);
760         } else {
761             logerr("%-32s copy: open failed: %s\n",
762                 (dpath ? dpath : spath),
763                 strerror(errno)
764             );
765             ++r;
766         }
767         free(path);
768
769         if (hln) {
770             if (!r && stat(dpath, &st2) == 0)
771                 hln->dino = st2.st_ino;
772             else
773                 hltdelete(hln);
774         }
775     } else if (S_ISLNK(st1.st_mode)) {
776         char link1[1024];
777         char link2[1024];
778         char path[2048];
779         int n1;
780         int n2;
781
782         snprintf(path, sizeof(path), "%s.tmp", dpath);
783         n1 = readlink(spath, link1, sizeof(link1) - 1);
784         n2 = readlink(dpath, link2, sizeof(link2) - 1);
785         if (n1 >= 0) {
786             if (ForceOpt || n1 != n2 || bcmp(link1, link2, n1) != 0) {
787                 umask(~st1.st_mode);
788                 remove(path);
789                 link1[n1] = 0;
790                 if (symlink(link1, path) < 0) {
791                       logerr("%-32s symlink (%s->%s) failed: %s\n",
792                           (dpath ? dpath : spath), link1, path,
793                           strerror(errno)
794                       );
795                       ++r;
796                 } else {
797                     lchown(path, st1.st_uid, st1.st_gid);
798                     /*
799                      * there is no lchmod() or lchflags(), we 
800                      * cannot chmod or chflags a softlink.
801                      */
802                     if (xrename(path, dpath, st2.st_flags) != 0) {
803                         logerr("%-32s rename softlink (%s->%s) failed: %s\n",
804                             (dpath ? dpath : spath),
805                             path, dpath, strerror(errno));
806                     } else if (VerboseOpt) {
807                         logstd("%-32s softlink-ok\n", (dpath ? dpath : spath));
808                     }
809                     umask(000);
810                     CountWriteBytes += n1;
811                     CountCopiedItems++;
812                 }
813             } else {
814                 if (VerboseOpt >= 3)
815                     logstd("%-32s nochange\n", (dpath ? dpath : spath));
816             }
817             CountSourceBytes += n1;
818             CountReadBytes += n1;
819             if (n2 > 0) CountReadBytes += n2;
820             CountSourceItems++;
821         } else {
822             r = 1;
823             logerr("%-32s softlink-failed\n", (dpath ? dpath : spath));
824         }
825     } else if (S_ISCHR(st1.st_mode) || S_ISBLK(st1.st_mode)) {
826         char path[2048];
827
828         if (ForceOpt ||
829             st2Valid == 0 || 
830             st1.st_mode != st2.st_mode || 
831             st1.st_rdev != st2.st_rdev ||
832             st1.st_uid != st2.st_uid ||
833             st1.st_gid != st2.st_gid
834         ) {
835             snprintf(path, sizeof(path), "%s.tmp", dpath);
836
837             remove(path);
838             if (mknod(path, st1.st_mode, st1.st_rdev) == 0) {
839                 chmod(path, st1.st_mode);
840                 chown(path, st1.st_uid, st1.st_gid);
841                 remove(dpath);
842                 if (xrename(path, dpath, st2.st_flags) != 0) {
843                     logerr("%-32s dev-rename-after-create failed: %s\n",
844                         (dpath ? dpath : spath),
845                         strerror(errno)
846                     );
847                 } else if (VerboseOpt) {
848                     logstd("%-32s dev-ok\n", (dpath ? dpath : spath));
849                 }
850                 CountCopiedItems++;
851             } else {
852                 r = 1;
853                 logerr("%-32s dev failed: %s\n", 
854                     (dpath ? dpath : spath), strerror(errno)
855                 );
856             }
857         } else {
858             if (VerboseOpt >= 3)
859                 logstd("%-32s nochange\n", (dpath ? dpath : spath));
860         }
861         CountSourceItems++;
862     }
863     ResetList(&list);
864     return (r);
865 }
866
867 /*
868  * RemoveRecur()
869  */
870
871 void
872 RemoveRecur(const char *dpath, dev_t devNo)
873 {
874     struct stat st;
875
876     if (lstat(dpath, &st) == 0) {
877         if ((int)devNo < 0)
878             devNo = st.st_dev;
879         if (st.st_dev == devNo) {
880             if (S_ISDIR(st.st_mode)) {
881                 DIR *dir;
882
883                 if ((dir = opendir(dpath)) != NULL) {
884                     struct dirent *den;
885                     while ((den = readdir(dir)) != NULL) {
886                         char *ndpath;
887
888                         if (strcmp(den->d_name, ".") == 0)
889                             continue;
890                         if (strcmp(den->d_name, "..") == 0)
891                             continue;
892                         ndpath = mprintf("%s/%s", dpath, den->d_name);
893                         RemoveRecur(ndpath, devNo);
894                         free(ndpath);
895                     }
896                     closedir(dir);
897                 }
898                 if (AskConfirmation && NoRemoveOpt == 0) {
899                     if (YesNo(dpath)) {
900                         if (rmdir(dpath) < 0) {
901                             logerr("%-32s rmdir failed: %s\n",
902                                 dpath, strerror(errno)
903                             );
904                         }
905                         CountRemovedItems++;
906                     }
907                 } else {
908                     if (NoRemoveOpt) {
909                         if (VerboseOpt)
910                             logstd("%-32s not-removed\n", dpath);
911                     } else if (rmdir(dpath) == 0) {
912                         if (VerboseOpt)
913                             logstd("%-32s rmdir-ok\n", dpath);
914                         CountRemovedItems++;
915                     } else {
916                         logerr("%-32s rmdir failed: %s\n",
917                             dpath, strerror(errno)
918                         );
919                     }
920                 }
921             } else {
922                 if (AskConfirmation && NoRemoveOpt == 0) {
923                     if (YesNo(dpath)) {
924                         if (remove(dpath) < 0) {
925                             logerr("%-32s remove failed: %s\n",
926                                 dpath, strerror(errno)
927                             );
928                         }
929                         CountRemovedItems++;
930                     }
931                 } else {
932                     if (NoRemoveOpt) {
933                         if (VerboseOpt)
934                             logstd("%-32s not-removed\n", dpath);
935                     } else if (remove(dpath) == 0) {
936                         if (VerboseOpt)
937                             logstd("%-32s remove-ok\n", dpath);
938                         CountRemovedItems++;
939                     } else {
940                         logerr("%-32s remove failed: %s\n",
941                             dpath, strerror(errno)
942                         );
943                     }
944                 }
945             }
946         }
947     }
948 }
949
950 void
951 InitList(List *list)
952 {
953     bzero(list, sizeof(List));
954     list->li_Node.no_Next = &list->li_Node;
955 }
956
957 void 
958 ResetList(List *list)
959 {
960     Node *node;
961
962     while ((node = list->li_Node.no_Next) != &list->li_Node) {
963         list->li_Node.no_Next = node->no_Next;
964         free(node);
965     }
966     InitList(list);
967 }
968
969 int
970 AddList(List *list, const char *name, int n)
971 {
972     Node *node;
973     int hv;
974
975     hv = shash(name);
976
977     /*
978      * Scan against wildcards.  Only a node value of 1 can be a wildcard
979      * ( usually scanned from .cpignore )
980      */
981
982     for (node = list->li_Hash[0]; node; node = node->no_HNext) {
983         if (strcmp(name, node->no_Name) == 0 ||
984             (n != 1 && node->no_Value == 1 && WildCmp(node->no_Name, name) == 0)
985         ) {
986             return(node->no_Value);
987         }
988     }
989
990     /*
991      * Look for exact match
992      */
993
994     for (node = list->li_Hash[hv]; node; node = node->no_HNext) {
995         if (strcmp(name, node->no_Name) == 0) {
996             return(node->no_Value);
997         }
998     }
999     node = malloc(sizeof(Node) + strlen(name) + 1);
1000     if (node == NULL) {
1001         fprintf(stderr, "out of memory\n");
1002         exit(EXIT_FAILURE);
1003     }
1004
1005     node->no_Next = list->li_Node.no_Next;
1006     list->li_Node.no_Next = node;
1007
1008     node->no_HNext = list->li_Hash[hv];
1009     list->li_Hash[hv] = node;
1010
1011     strcpy(node->no_Name, name);
1012     node->no_Value = n;
1013
1014     return(n);
1015 }
1016
1017 static int
1018 shash(const char *s)
1019 {
1020     int hv;
1021
1022     hv = 0xA4FB3255;
1023
1024     while (*s) {
1025         if (*s == '*' || *s == '?' || 
1026             *s == '{' || *s == '}' || 
1027             *s == '[' || *s == ']' ||
1028             *s == '|'
1029         ) {
1030             return(0);
1031         }
1032         hv = (hv << 5) ^ *s ^ (hv >> 23);
1033         ++s;
1034     }
1035     return(((hv >> 16) ^ hv) & HMASK);
1036 }
1037
1038 /*
1039  * WildCmp() - compare wild string to sane string
1040  *
1041  *      Return 0 on success, -1 on failure.
1042  */
1043
1044 int
1045 WildCmp(const char *w, const char *s)
1046 {
1047     /*
1048      * skip fixed portion
1049      */
1050   
1051     for (;;) {
1052         switch(*w) {
1053         case '*':
1054             if (w[1] == 0)      /* optimize wild* case */
1055                 return(0);
1056             {
1057                 int i;
1058                 int l = strlen(s);
1059
1060                 for (i = 0; i <= l; ++i) {
1061                     if (WildCmp(w + 1, s + i) == 0)
1062                         return(0);
1063                 }
1064             }
1065             return(-1);
1066         case '?':
1067             if (*s == 0)
1068                 return(-1);
1069             ++w;
1070             ++s;
1071             break;
1072         default:
1073             if (*w != *s)
1074                 return(-1);
1075             if (*w == 0)        /* terminator */
1076                 return(0);
1077             ++w;
1078             ++s;
1079             break;
1080         }
1081     }
1082     /* not reached */
1083     return(-1);
1084 }
1085
1086 int
1087 YesNo(const char *path)
1088 {
1089     int ch, first;
1090
1091     fprintf(stderr, "remove %s (Yes/No) [No]? ", path);
1092     fflush(stderr);
1093
1094     first = ch = getchar();
1095     while (ch != '\n' && ch != EOF)
1096         ch = getchar();
1097     return ((first == 'y' || first == 'Y'));
1098 }
1099
1100 /*
1101  * xrename() - rename with override
1102  *
1103  *      If the rename fails, attempt to override st_flags on the 
1104  *      destination and rename again.  If that fails too, try to
1105  *      set the flags back the way they were and give up.
1106  */
1107
1108 static int
1109 xrename(const char *src, const char *dst, u_long flags)
1110 {
1111     int r;
1112
1113     r = 0;
1114
1115     if ((r = rename(src, dst)) < 0) {
1116         chflags(dst, 0);
1117         if ((r = rename(src, dst)) < 0)
1118                 chflags(dst, flags);
1119     }
1120     return(r);
1121 }
1122
1123 static int
1124 xlink(const char *src, const char *dst, u_long flags)
1125 {
1126     int r, e;
1127
1128     r = 0;
1129
1130     if ((r = link(src, dst)) < 0) {
1131         chflags(src, 0);
1132         r = link(src, dst);
1133         e = errno;
1134         chflags(src, flags);
1135         errno = e;
1136     }
1137     return(r);
1138 }
1139