/* * CDE - Common Desktop Environment * * Copyright (c) 1993-2012, The Open Group. All rights reserved. * * These libraries and programs are free software; you can * redistribute them and/or modify them under the terms of the GNU * Lesser General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) * any later version. * * These libraries and programs are distributed in the hope that * they will be useful, but WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public * License along with these libraries and programs; if not, write * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth * Floor, Boston, MA 02110-1301 USA */ /* $TOG: dosync.c /main/6 1998/10/26 12:40:53 mgreess $ */ /************************************<+>************************************* **************************************************************************** * * FILE: dosync.c * * * DESCRIPTION: Routines synchronizing directories * * FUNCTIONS: CONFIRM * CmpPred * DTYPE * DirCmp * DirCmp2 * DumpDir * ERROR_CHECK * FreeDir * GetDir * GetDirEntry * SortDir * SyncDirRecur * SyncDirectory * SyncItem * cmpSymlink * dirEraseCallback * dirErrorCallback * doCopy * doCopyLink * doMkdir * doSymlink * doUnlink * targetLink * * (c) Copyright 1993, 1994, 1995 Hewlett-Packard Company * (c) Copyright 1993, 1994, 1995 International Business Machines Corp. * (c) Copyright 1993, 1994, 1995 Sun Microsystems, Inc. * (c) Copyright 1993, 1994, 1995 Novell, Inc. * **************************************************************************** ************************************<+>*************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include "fsrtns.h" #ifdef PATRTNS #include "patrtns.h" #endif #include "dosync.h" /*-------------------------------------------------------------------- * global variables *------------------------------------------------------------------*/ char *FileOpNames[] = { "synchronize", "opendir", "lstat", "stat", "readlink", "delete", "copy", "mkdir", "copy link", "make link", "working", "is a copy", "is a link" }; int (*syncConfirmCallback)(FileOp op, char *sname, int stype, char *tname, int ttype, char *link) = NULL; int (*syncErrorCallback)(FileOp op, char *fname, int errnum) = NULL; static SyncParams *SP; /*-------------------------------------------------------------------- * macros for invoking callback functions *------------------------------------------------------------------*/ #define DTYPE(delP) ((delP)? (delP)->ftype: ft_noent) #define CONFIRM(op, sn, st, tn, tt, l, rc) \ if (periodicCallback && (*periodicCallback)() != 0) return -1; \ else if (syncConfirmCallback && \ (rc = syncConfirmCallback(op, sn, st, tn, tt, l)) != 0) return rc; else #define ERROR_CHECK(op, f, rc) \ if (rc < 0 || rc && syncErrorCallback && syncErrorCallback(op, f, rc) < 0) \ return -1; else /*-------------------------------------------------------------------- * reading directories *------------------------------------------------------------------*/ /* structure containing information anout a directory entry */ typedef struct de { char name[256]; /* file name */ int rc, lrc; /* return codes from stat, lstat */ struct stat stat; /* stat */ int ftype; /* file type flags (see #defines in dosync.h) */ char exclude, skip; /* exclude, skip flags */ char *link; /* value of symbolic link */ struct de *teP; /* source dir: points to correspond. target dir entry */ struct de *next; /* next directory entry */ } DirEntry; /* * DumpDir: dump linked list of directory entries to stdout (debugging only) */ static void DumpDir(DirEntry *dP) { DirEntry *eP; for (eP = dP; eP; eP = eP->next) printf(" %-15s %2d %c %c %c %c %c %s\n", eP->name, eP->rc, (eP->ftype & ft_isdir)? 'D': ' ', (eP->ftype & ft_islnk)? 'L': ' ', eP->exclude? 'X': ' ', eP->skip? 'S': ' ', eP->teP? 'F': ' ', eP->link? eP->link: ""); } /* * DirCmp: compare function for sorting directory entries */ static int DirCmp(const void *p1, const void *p2) { return strcmp((*(DirEntry **)p1)->name, (*(DirEntry **)p2)->name); } /* * SortDir: sort linked list of directory entries by name */ static DirEntry * SortDir(DirEntry *dP, int (*cmp)(const void*, const void*)) { int i, n; DirEntry *eP, **linkPP; DirEntry *da[1024], **daP; /* count number of directory entries */ n = 0; for (eP = dP; eP; eP = eP->next) n++; if (n < 2) return dP; /* allocate pointer array */ if (n <= 1024) daP = da; else daP = (DirEntry **)malloc(n * sizeof(DirEntry *)); /* sort pointer array */ for (eP = dP, i = 0; eP; eP = eP->next, i++) daP[i] = eP; qsort(daP, n, sizeof(DirEntry *), cmp); /* re-link the linked list */ linkPP = &dP; for (i = 0; i < n; i++) { *linkPP = daP[i]; linkPP = &(*linkPP)->next; } *linkPP = NULL; /* free pointer array */ if (daP != da) free(daP); return dP; } /* * FreeDir: de-allocate linked list of directory entries */ static void FreeDir(DirEntry *deP) { DirEntry *nextP; while (deP) { nextP = deP->next; free(deP); deP = nextP; } } /* * GetDirEntry: Get information about one directory entry. * If an error occurs, returns the errno in *rc and the operation * that failed (op_lstat, op_stat, or op_readlink) in *op. */ static DirEntry * GetDirEntry(char *fname, FileOp *op, int *rc) { DirEntry *deP; char *p; char link[1024]; int l; /* allocate new DirEntry */ deP = (DirEntry *)malloc(sizeof(DirEntry)); memset(deP, 0, sizeof(DirEntry)); /* get file name */ p = strrchr(fname, '/'); if (p && p > fname) snprintf(deP->name, sizeof(deP->name), "%s", p + 1); else snprintf(deP->name, sizeof(deP->name), "%s", fname); /* assume everything is fine */ *op = 0; *rc = 0; /* get information */ deP->rc = lstat(fname, &deP->stat); if (deP->rc) { *op = op_lstat; *rc = errno; } else if ((deP->stat.st_mode & S_IFMT) == S_IFDIR) deP->ftype |= ft_isdir; else if ((deP->stat.st_mode & S_IFMT) == S_IFLNK) { deP->ftype |= ft_islnk; deP->lrc = stat(fname, &deP->stat); if (deP->lrc) { if (errno != ENOENT) { *op = op_stat; *rc = errno; } } else if ((deP->stat.st_mode & S_IFMT) == S_IFDIR) deP->ftype |= ft_isdir; l = readlink(fname, link, sizeof(link) - 1); if (l < 0) { *op = op_readlink; *rc = errno; } else { link[l] = 0; deP->link = strdup(link); } } return deP; } /* * GetDir: read a directory and return linked list of directory entries */ static int GetDir(char *dirname, PatternList *xl, PatternList *sl, DirEntry **listPP) { DIR *dirP = NULL; /* open directory */ struct dirent *entryP; /* directory entry */ DirEntry *deP, *firstP, **linkPP; char fname[1024], *fnP; PatternList *pl; FileOp op; int rc; /* open directory */ dirP = opendir(dirname); if (dirP == NULL) { rc = errno; if (syncErrorCallback && syncErrorCallback(op_opendir, dirname, rc) < 0) { return -1; } return rc; } /* copy dirname to file name buffer */ snprintf(fname, sizeof(fname), "%s", dirname); fnP = fname + strlen(fname); *fnP++ = '/'; /* initialize linked list */ firstP = NULL; linkPP = &firstP; /* read the directory */ while ((entryP = readdir(dirP)) != NULL) { /* skip . and .. */ if (strcmp(entryP->d_name, ".") == 0 || strcmp(entryP->d_name, "..") == 0) continue; /* get new DirEntry */ strcpy(fnP, entryP->d_name); deP = GetDirEntry(fname, &op, &rc); /* add to linked list */ *linkPP = deP; linkPP = &deP->next; /* if error occurred, call error callback function */ if (rc != 0 && syncErrorCallback && syncErrorCallback(op, fname, rc) < 0) { closedir(dirP); FreeDir(firstP); return -1; } /* check exclude and skip list */ #ifdef PATRTNS for (pl = xl ; pl; pl = pl->next) if (MatchPattern(pl->pattern, fname)) deP->exclude++; for (pl = sl; pl; pl = pl->next) if (MatchPattern(pl->pattern, fname)) deP->skip++; #endif } closedir(dirP); *listPP = SortDir(firstP, DirCmp); return 0; } /*-------------------------------------------------------------------- * file operations *------------------------------------------------------------------*/ static int erase_rc; /* * dirEraseCallback: used in doUnlink (below) for confirmation * during recursive directory deletes */ static int dirEraseCallback(char *fname) { erase_rc = syncConfirmCallback(op_delete, "", ft_noent, fname, 0, ""); return erase_rc; } static int dirErrorCallback(char *fname, int errnum) { erase_rc = syncErrorCallback(op_delete, fname, errnum); return erase_rc; } /* * doUnlink: delete a file from the target directory */ static int doUnlink(char *fname, DirEntry *fP, int confirm) { int rc; if (confirm) CONFIRM(op_delete, "", ft_noent, fname, fP->ftype, "", rc); if (SP->dontdoit) rc = 0; else if (SP->keepold) { char newname[1024]; snprintf(newname, sizeof(newname), "%s%s", fname, SP->keepold); fsMove(fname, newname, 1, &rc); } else if ((fP->ftype & ft_isdir) && !(fP->ftype & ft_islnk)) { /* arrange for syncConfirmCallback's during recursive directory deletes */ erase_rc = 0; if (syncErrorCallback) errorCallback = dirErrorCallback; if (syncConfirmCallback) progressCallback = dirEraseCallback; fsErase(fname, &rc, 1); if (rc < 0 && erase_rc) rc = ENOTEMPTY; errorCallback = NULL; progressCallback = NULL; } else rc = (unlink(fname) != 0)? errno: 0; /* check for errors */ ERROR_CHECK(op_delete, fname, rc); return rc; } /* * doCopy: copy a file from the source to target directory */ static int doCopy(char *sname, DirEntry *sP, char *tname, DirEntry *delP) { int rc; /* display message */ CONFIRM(op_copy, sname, sP->ftype, tname, DTYPE(delP), "", rc); /* if necessary, delete target */ if (delP && !SP->dontdoit) if ((rc = doUnlink(tname, delP, 0)) != 0) return rc; /* copy the file */ if (SP->dontdoit) rc = 0; else fsCopy(sname, tname, 0, &rc); /* check for errors */ ERROR_CHECK(op_copy, sname, rc); return rc; } /* * doMkdir: create a new sub directory in the target directory */ static int doMkdir(char *sname, DirEntry *sP, char *tname, DirEntry *delP) { int rc; /* display message */ CONFIRM(op_mkdir, sname, sP->ftype, tname, DTYPE(delP), "", rc); /* if necessary, delete target */ if (delP && !SP->dontdoit) if ((rc = doUnlink(tname, delP, 0)) != 0) return rc; /* make a new directory */ rc = SP->dontdoit? 0: (mkdir(tname, sP->stat.st_mode & 07777) < 0)? errno: 0; /* check for errors */ ERROR_CHECK(op_mkdir, tname, rc); return rc; } /* * targetLink: lookup symbloc link in the maplink list */ static void targetLink(char *source, char *target, char **tlPP, char **msgPP) /* * */ { static char buf[1024] = " -> "; MapList *ml; int l; for (ml = SP->maplink; ml; ml = ml->next) { l = strlen(ml->from); if (strncmp(ml->from, source, l) == 0 && (source[l] == '/' || source[l] == 0)) { strcpy(buf + 4, ml->to); if (source[l]) strcat(buf, source +l); if (strcmp(buf + 4, target) != 0) { *tlPP = buf + 4; *msgPP = buf; return; } } } *tlPP = source; *msgPP = ""; } /* * doCopyLink: copy a symbolic link from the source to target directory */ static int doCopyLink(char *sname, DirEntry *sP, char *tname, DirEntry *delP) { char *tl, *msg; int rc; /* determine where the link should point */ targetLink(sP->link, tname, &tl, &msg); /* display message */ CONFIRM(op_cplink, sname, sP->ftype, tname, DTYPE(delP), msg, rc); /* if necessary, delete target */ if (delP && !SP->dontdoit) if ((rc = doUnlink(tname, delP, 0)) != 0) return rc; /* create link */ rc = SP->dontdoit? 0: (symlink(tl, tname) != 0)? errno: 0; /* check for errors */ ERROR_CHECK(op_cplink, tname, rc); return rc; } /* * doSymlink: create a symbolic link in the target directory to a file * in the source directory */ static int doSymlink(char *sname, DirEntry *sP, char *tname, DirEntry *delP) { char *tl, *msg; int rc; /* determine where the link should point */ targetLink(sname, tname, &tl, &msg); /* display message */ CONFIRM(op_mklink, sname, sP->ftype, tname, DTYPE(delP), msg, rc); /* if necessary, delete target */ if (delP && !SP->dontdoit) if ((rc = doUnlink(tname, delP, 0)) != 0) return rc; /* create link */ targetLink(sname, tname, &tl, &msg); rc = SP->dontdoit? 0: (symlink(tl, tname) != 0)? errno: 0; /* check for errors */ ERROR_CHECK(op_mklink, sname, rc); return rc; } /* * cmpSymlink: compare two symbolic links * return values: * -1 error reading one of the links * 0 links point to the same file * 1 tname link points to sname * 2 links point to different files */ static int cmpSymlink(char *sname, DirEntry *seP, char *tname, DirEntry *teP) { char *tl, *msg; /* return error if target link could not be read */ if (!teP->link) return -1; /* check if target link points to source link */ if (SP->keeplinks) { targetLink(sname, tname, &tl, &msg); if (strcmp(teP->link, tl) == 0) return 1; } /* if source not a symbolic link, target must be pointing to wrong file */ if (!(seP->ftype & ft_islnk)) return 2; /* compare the links */ targetLink(seP->link, tname, &tl, &msg); if (strcmp(teP->link, tl) == 0) return 0; else return 2; } /*-------------------------------------------------------------------- * sync directories *------------------------------------------------------------------*/ static int SyncDirRecur(char *sdir, char *tdir); /* * SyncItem: copy or replace one item from the source to the target directory */ static int SyncItem(char *sname, DirEntry *sP, char *tname, DirEntry *tP) { int rc; if (periodicCallback && (*periodicCallback)() != 0) return -1; if (!tP) { /* target does not exist */ rpl_target: /*-------------------------------------------------------------------- * target does not exist or should be replaced *------------------------------------------------------------------*/ /* check if -dontreplace or -dontadd options apply */ if (tP && SP->dontreplace || !tP && SP->dontadd) return 0; if (sP->ftype & ft_islnk) { /* source is a link */ if ((sP->ftype & ft_isdir) && !SP->copydirs || !(sP->ftype & ft_isdir) && !SP->copyfiles) { /* just copy the link */ return doCopyLink(sname, sP, tname, tP); } } else { /* source is not a link */ if ((sP->ftype & ft_isdir) && SP->linkdirs || !(sP->ftype & ft_isdir) && SP->linkfiles) { /* don't copy; just create a link to the target */ return doSymlink(sname, sP, tname, tP); } } if (sP->ftype & ft_isdir) { /* source is a dir: create target directory and do recursive sync */ rc = doMkdir(sname, sP, tname, tP); if (rc || SP->dontdoit) return rc; else return SyncDirRecur(sname, tname); } else /* source is a regular file: copy the file */ return doCopy(sname, sP, tname, tP); } else if (tP->ftype & ft_islnk) { /*-------------------------------------------------------------------- * target is a symbolic link *------------------------------------------------------------------*/ if ((tP->ftype & ft_isdir) == (sP->ftype & ft_isdir)) { /* * target points to the right type of object (dir vs regular file): * check if target points to the right place */ rc = cmpSymlink(sname, sP, tname, tP); if (rc < 0) return 1; /* something is wrong */ if (SP->listlinks && rc == 1) CONFIRM(op_info_link, sname, sP->ftype, tname, tP->ftype, "", rc); if (rc < 2) /* target is pointing to the right place */ return 0; } /* * target points to the wrong type of object: * delete target link and try again */ goto rpl_target; } else if (!(tP->ftype & ft_isdir)) { /*-------------------------------------------------------------------- * target is regular file *------------------------------------------------------------------*/ if (sP->ftype & ft_isdir) /* source is a dir: delete target file and try again */ goto rpl_target; else { /* source is regular file or link to regular file */ if (sP->ftype & ft_islnk) { /* source is just a link */ if (!SP->keepcopies && !SP->copyfiles) /* replace target by a link */ goto rpl_target; else if (SP->listcopies) CONFIRM(op_info_copy, sname, sP->ftype, tname, tP->ftype, "", rc); } if (!SP->forcecopies && tP->stat.st_mtime == sP->stat.st_mtime && tP->stat.st_size == sP->stat.st_size) { /* we assume the file hasn't changed: don't do anything */ return 0; } if (SP->keepnew && tP->stat.st_mtime > sP->stat.st_mtime) /* we should keep the target because it is newer than the source */ return 0; /* replace the target file */ goto rpl_target; } } else if (!(tP->ftype & ft_islnk)) { /*-------------------------------------------------------------------- * target is a directory *------------------------------------------------------------------*/ if (sP->ftype & ft_isdir) { if (sP->ftype & ft_islnk) { /* source is just a link */ if (!SP->keepcopies && !SP->copydirs) /* replace target by a link */ goto rpl_target; else if (SP->listcopies) CONFIRM(op_info_copy, sname, sP->ftype, tname, tP->ftype, "", rc); } /* source is dir: recursively sync source and target dirs */ return SyncDirRecur(sname, tname); } else /* source is no dir: delete target dir and try again */ goto rpl_target; } /* should never be reached */ return -1; } /* * CmpPred: returns a precedence number that is used to determine in which * order source directory entries are processed: * 1 source is regular file * 2 source is symbolic link * 3 target is not a real directory * 4 target exists and is a real directory (not a sym link) */ static int CmpPred(DirEntry *dP) { if (!(dP->ftype & ft_isdir)) return 1; else if (dP->ftype & ft_islnk) return 2; else if (!dP->teP || !(dP->teP->ftype & ft_isdir) || (dP->teP->ftype & ft_islnk)) { return 3; } else return 4; } /* * DirCmp2: compare function for sorting directory entries */ static int DirCmp2(const void *p1, const void *p2) { int d = CmpPred(*(DirEntry **)p1) - CmpPred(*(DirEntry **)p2); if (d) return d; else return strcmp((*(DirEntry **)p1)->name, (*(DirEntry **)p2)->name); } /* * SyncDirRecur: recursively synchronize source and target directories */ static int SyncDirRecur(char *sdir, char *tdir) { DirEntry *sP, *tP, *seP, *teP; char *fsP, *ftP; int rc; /* informational callback */ if (SP->verbose) CONFIRM(op_sync, sdir, ft_isdir, tdir, ft_isdir, "", rc); /* read source and target directories */ if ((rc = GetDir(sdir, SP->exclude, SP->skip, &sP)) != 0) return rc; if ((rc = GetDir(tdir, NULL, SP->skip, &tP)) != 0) { FreeDir(sP); return rc; } /* append "/" to source and target directory names */ fsP = sdir + strlen(sdir); ftP = tdir + strlen(tdir); *fsP++ = '/'; *ftP++ = '/'; /* first delete files in target dir that don't exist in source dir */ seP = sP; teP = tP; while (teP) { /* compare directory entries */ rc = (seP != NULL)? strcmp(seP->name, teP->name): 1; if ((seP != NULL) && seP->exclude) { /* skip excluded files */ seP = seP->next; } else if (rc == 0) { /* file exists in both directories */ seP->teP = teP; seP = seP->next; teP = teP->next; } else if (rc < 0) { /* source file not in target directory */ seP = seP->next; } else { /* rc > 0 */ /* target file not in source directory */ if (!SP->dontdelete && !teP->skip) { strcpy(ftP, teP->name); if (doUnlink(tdir, teP, 1) < 0) { FreeDir(sP); FreeDir(tP); return -1; } } teP = teP->next; } } /* re-sort source dir so that regular files are processed before sub dirs */ sP = SortDir(sP, DirCmp2); /* process all entires found in the source directory */ for (seP = sP, rc = 0; seP && rc >= 0; seP = seP->next) { /* skip error entries */ if (seP->rc) continue; /* skip excluded and to-be-skipped files */ if (seP->exclude || seP->skip) continue; /* also skip sub directories if "don't recur" option is set */ if (SP->dontrecur && (seP->ftype & ft_isdir)) continue; /* construct source and target file names */ strcpy(fsP, seP->name); strcpy(ftP, seP->name); /* sync directory item */ rc = SyncItem(sdir, seP, tdir, seP->teP); } /* free directory lists */ FreeDir(sP); FreeDir(tP); return (rc < 0)? -1: 0; } /*-------------------------------------------------------------------- * external entry point *------------------------------------------------------------------*/ void SyncDirectory(SyncParams *p) { char sbuf[1024], tbuf[1024]; DirEntry *sP, *tP; FileOp op; int rc; /* the lower-level error and progress callbacks are not used */ errorCallback = NULL; progressCallback = NULL; /* save pointer to params; copy source & target names */ SP = p; snprintf(sbuf, sizeof(sbuf), "%s", SP->source); snprintf(tbuf, sizeof(tbuf), "%s", SP->target); /* get info about the source */ sP = GetDirEntry(sbuf, &op, &rc); if (rc) { if (syncErrorCallback) syncErrorCallback(op, sbuf, rc); FreeDir(sP); return; } /* * The "copytop" option means: if the top-level source is a symbolic * make a copy of the real thing. To get this behavior we just switch * off the ft_islnk flag on the source. */ if (SP->copytop) sP->ftype &= ~ft_islnk; /* get info about the target */ tP = GetDirEntry(tbuf, &op, &rc); if (rc) { if (op == op_lstat && rc == ENOENT) { /* target does not yet exist; that's ok */ FreeDir(tP); tP = NULL; } else { /* we'll try to procede anyway ... unless the error callback says no */ if (syncErrorCallback && syncErrorCallback(op, tbuf, rc) < 0) { FreeDir(sP); FreeDir(tP); return; } } } /* now do the real work */ SyncItem(sbuf, sP, tbuf, tP); /* free storage and return */ FreeDir(sP); FreeDir(tP); }