/* Make links utility * Greg Lehey, LEMIS * * $Id: mklinks.c,v 1.9 2005/07/16 03:08:42 grog Exp grog $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* How much should we tell the user about what we're doing? * * 0: nothing * 1: directory creation * 2: file link, unlink * 3: files that are the same */ int verbose = 0; int interactive; /* set if we're talking to a tty */ int compare_files = 1; /* compare files before prompting for replacement */ int delete_files = 0; /* delete instead of link */ int delete_dirs = 0; /* delete directories when deleting files */ int prompt = 0; /* prompt before changing */ int replace_newer = 0; /* don't replace files with newer versions */ int create_symlinks = 0; /* create symlink flag: */ #define REPLACE_SYMLINK 1 /* create if the old file was symlink */ #define SYMLINK_FILES 2 /* force a symlink if this is a regular file */ #define SYMLINK_DIRECTORIES 4 /* symlink directories too */ #define SYMLINK_ORIGINAL 8 /* set if we want to deference source symlinks */ int keep_different = 0; /* If set, keep different files, else prompt */ int want_diff = 0; /* do a diff to screen before replacing */ int linknew = 1; /* create a link if file doesn't already exist */ int symlink_to_broken = 1; /* create a symlink to a broken symlink */ int pretending = 0; /* not a pretending run unless we say -z */ int makedirs = 1; /* make directories which don't exist */ int makefiles = 1; /* make files which don't exist */ int samefs = 0; /* set if we're on the same file system */ int update_only = 0; /* only replace with newer files */ struct termios current_status; /* current termios flags */ struct termios noicanon_status; /* and the same with icanon reset */ const int Stdin = 0; /* and we can't use stdin here because * of the way it's defined */ enum filetype { ft_none, /* doesn't exist */ ft_file, /* regular file */ ft_symlink, /* symbolic link, */ ft_broken_symlink, /* broken symbolic link */ ft_directory /* and directory */ }; /* The following information describes the source and destination files */ struct file_info { char name [MAXPATHLEN]; /* name of the file */ enum filetype type; /* type of file (see enum filetype above) */ struct stat stat; /* and status for the file */ char linkname [MAXPATHLEN]; /* name of the real file, only valid if type * is 'ft_symlink' */ struct stat linkstat; /* and its link */ int linkloop; /* set if it's a looping symlink */ }; /* base names of the hierarchies */ struct file_info basesrc; struct file_info basedst; #if ! defined (__FreeBSD__) && ! defined (__NetBSD__) && ! defined (__linux__) extern char *sys_errlist []; extern int sys_nerr; char *strerror (int errnum) { if (errnum < sys_nerr) return sys_errlist [errnum]; return "(Unknown error number)"; } #endif void checkdir (struct file_info *src, struct file_info *dst); #ifdef __linux__ /* * Linux doesn't have strlcat and strlcpy. We could substitute, but * it's easy enough to incorporate the correct functions. This is a * modified version of the original. */ /* $OpenBSD: strlcat.c,v 1.2 1999/06/17 16:28:58 millert Exp $ */ /* * Copyright (c) 1998 Todd C. Miller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Appends src to string dst of size siz (unlike strncat, siz is the * full size of dst, not space left). At most siz-1 characters * will be copied. Always NUL terminates (unless siz <= strlen(dst)). * Returns strlen(src) + MIN(siz, strlen(initial dst)). * If retval >= siz, truncation occurred. */ size_t strlcat(char *dst, const char *src, size_t siz ) { char *d = dst; const char *s = src; size_t n = siz; size_t dlen; /* Find the end of dst and adjust bytes left but don't go past end */ while (n-- != 0 && *d != '\0') d++; dlen = d - dst; n = siz - dlen; if (n == 0) return(dlen + strlen(s)); while (*s != '\0') { if (n != 1) { *d++ = *s; n--; } s++; } *d = '\0'; return(dlen + (s - src)); /* count does not include NUL */ } /* * Copy src to string dst of size siz. At most siz-1 characters * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen(src); if retval >= siz, truncation occurred. */ size_t strlcpy (char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0 && --n != 0) { do { if ((*d++ = *s++) == 0) break; } while (--n != 0); } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } return(s - src - 1); /* count does not include NUL */ } #endif void Exit (int status) { if (interactive && tcsetattr (Stdin, TCSANOW, ¤t_status)) /* can't set attributes? */ { perror ("Can't set stdin attributes"); /* shouldn't be able to happen */ exit (1); } exit (status); } /* Return absolute form of a filename. */ char *absname (char *name) { char *pathname; char *cp; int wdlen = 0; pathname = malloc (MAXPATHLEN); if (! pathname) { fprintf (stderr, "Can't allocate memory for file names\n"); exit (1); } pathname [0] = '\0'; if (name [0] != '/') { cp = getcwd (pathname, MAXPATHLEN); if (cp == NULL) { fprintf (stderr, "Can't get cwd: %s\n", strerror (errno)); exit (1); } wdlen = strlcpy (pathname, cp, MAXPATHLEN); strlcat (pathname, "/", MAXPATHLEN); wdlen--; } /* * Handle some of the more usual cases with . and .. . * This can do with improvement. */ if (strcmp (name, ".")) /* not this directory, can't ignore it */ { if (! strcmp (name, "..")) { char *c = strrchr (pathname, '/'); /* find last / */ if (c > pathname) /* not the root directory, */ *c = '\0'; /* truncate there */ } else /* not relative path, */ strlcat (pathname, name, MAXPATHLEN); /* add the name */ } return pathname; } /* ls_l: print file information in ls -l format */ void ls_l (char *name) { char permissions [] = "-rwxrwxrwx"; int i; int s; /* mode bits */ struct passwd *pwent; struct group *gid; char uname [16]; /* this 16 is tacky: username */ char group [16]; /* and group */ char *date_and_time; struct stat filestat; if (lstat (name, &filestat)) /* can't stat? */ { printf ("*** Can't stat %s: %s\n", name, strerror (errno)); return; } s = filestat.st_mode; /* mode bits */ for (i = 1; i < 10; i++) { if (! (s & 0400)) /* mode bit set? */ permissions [i] = '-'; /* no, clear it out */ s <<= 1; /* move next bit in */ } switch (filestat.st_mode & 0170000) /* file type */ { case 040000: /* directory */ permissions [0] = 'd'; break; case 0020000: /* char special (huh?) */ permissions [0] = 'c'; break; case 0060000: /* block special */ permissions [0] = 'b'; break; case 0010000: /* pipe */ permissions [0] = 'p'; break; case 0140000: permissions [0] = 's'; break; case 0120000: /* symlink */ permissions [0] = 'l'; break; default: permissions [0] = '?'; case 0100000: /* regular file */ break; } if ((pwent = getpwuid (filestat.st_uid))) /* got a user, */ strcpy (uname, pwent->pw_name); else /* no user name, */ sprintf (uname, "%d", filestat.st_uid); if ((gid = getgrgid (filestat.st_gid))) /* got a group, */ strcpy (group, gid->gr_name); else /* no group name, */ sprintf (group, "%d", filestat.st_gid); date_and_time = ctime (&filestat.st_mtime); /* last mod time */ date_and_time [strlen (date_and_time) - 1] = '\0'; /* get rid of this damn silly \n */ if (S_ISLNK (filestat.st_mode)) /* symlink, */ { char linkname [MAXPATHLEN]; int namelen = readlink (name, linkname, MAXPATHLEN); if (namelen == -1) /* couldn't read link? */ { strcpy (linkname, "Can't read link: "); /* put message instead of name */ strcat (linkname, strerror (errno)); } printf ("%s %2d %8s %8s %10ld %s %s -> %s\n", permissions, filestat.st_nlink, /* number of links */ uname, group, (long) filestat.st_size, /* file size */ date_and_time, name, linkname); } else printf ("%s %2d %8s %8s %10ld %s %s\n", permissions, filestat.st_nlink, /* number of links */ uname, group, (long) filestat.st_size, /* file size */ date_and_time, name); } /* List source and dest names */ void ls2 (struct file_info *src, struct file_info *dst) { ls_l (src->name); ls_l (dst->name); } char query () { char reply; if (interactive) /* somebody out there */ { tcflush (Stdin, TCIFLUSH); /* don't use typeahead */ if ((reply = getchar ()) != '\n') /* get a reply */ printf ("\n"); /* and finish the line */ if (reply == 'q') /* quit? */ exit (0); return reply; /* get the reply back */ } else /* batch */ { printf ("Can't query in non-interactive mode\n"); exit (1); } } /* * Replace a file with a link to the source file. If dosymlink is != * 0, the link will be a symlink. If it is 1, and the link doesn't * cycle, it will be a hard link. */ void dolink (struct file_info *src, struct file_info *dst, int dosymlink) { if (((src->stat.st_dev != dst->stat.st_dev) /* different device */ || src->linkloop) /* or symlink loop, */ && ! dosymlink ) /* and we want hard links */ { if (verbose > 1) fprintf (stderr, "*** %s is a symlink to parent directory %s, symlinking\n", src->linkname, src->name ); dosymlink = 1; /* make it a symlink */ } ls2 (src, dst); /* show what we had */ if (pretending) /* just showing what we'll do */ { if (dst->type != ft_none) printf ("--- unlink %s\n", dst->name); else if (! makefiles) /* no file, and we don't want one */ { printf (" Don't create file %s\n", dst->name); return; } if (dosymlink) printf ("+++ Symlink %s to %s\n", src->name, dst->name); else printf ("+++ Link %s to %s\n", src->name, dst->name); } else /* for real */ { if (dst->type != ft_none) { if (unlink (dst->name)) /* can't unlink the old source? */ { fprintf (stderr, "*** Can't unlink %s: %s\n", dst->name, strerror (errno)); return; } if (verbose > 1) fprintf (stderr, "--- Unlinked %s\n", dst->name); } else if (! makefiles) /* don't want to make new ones, */ { if (verbose) printf (" Don't create file %s\n", dst->name); return; } if (dosymlink) { if (symlink (src->name, dst->name)) /* can't link the new source? */ fprintf (stderr, "*** Can't symlink %s to %s: %s\n", src->name, dst->name, strerror (errno)); else if (verbose > 1) fprintf (stderr, "+++ Symlinked %s to %s\n", src->name, dst->name); } else { if (link (src->name, dst->name)) /* can't link the new source? */ fprintf (stderr, "*** Can't link %s to %s: %s\n", src->name, dst->name, strerror (errno)); else if (verbose > 1) fprintf (stderr, "+++ Linked %s to %s\n", src->name, dst->name); } } } /* * Try to delete a "directory". This could in fact be a symlink, * broken or otherwise. If it's a real directory, we don't check if * it's empty; unlink(2) does that for us. */ void delete_directory (struct file_info *file) { int error; if (pretending && (verbose > 1)) printf ("--- Delete empty directory %s\n", file->name); else { if (file->type == ft_directory) error = rmdir (file->name); else error = unlink (file->name); if (error) { if ((errno != ENOTEMPTY) && (errno != ENOENT) ) fprintf (stderr, "*** Couldn't unlink directory %s: %s\n", file->name, strerror (errno) ); } else if (verbose > 1) printf ("--- Deleted empty directory %s\n", file->name); } } int docompare (char *srcname, char *dstname) { char command [256]; sprintf (command, "cmp %s %s", srcname, dstname); return ! system (command); /* the files are the same */ } /* do a diff. If want_diff is 2, do a diff -w, else do a normal diff. */ int dodiff (char *srcname, char *dstname, int want_diff) { char command [256]; if (want_diff == 2) sprintf (command, "diff -wu %s %s | less", srcname, dstname); else sprintf (command, "diff -u %s %s | less", srcname, dstname); return ! system (command); /* the files are the same */ } /* * Check whether we should replace dst, for some definition of * "replace". In the case of deletions, this will be the decision to * remove it. Return 1 if it should be replaced, 0 otherwise. */ int check_replace (struct file_info *src, struct file_info *dst) { if ((src->stat.st_dev == dst->stat.st_dev) /* same device */ && (src->stat.st_ino == dst->stat.st_ino) ) /* and same file, */ /* * Both files are the same. * * If we're deleting, this always means "delete the dst file". * * If we're symlinking (SYMLINK_FILES), and the src file is a * regular file, and the dst file isn't a symlink, we replace it * with one, though I'm not sure what good that is. Note that in * this case we only unlink the dst file if the src file is a * regular file: if it's a symlink, it could be pointing to the * dst file, and that would lose it. * * On the other hand, if we're not symlinking, and the destination * file *is* a symlink, we replace it with a link. * * We don't have to deal with SYMLINK_DIRECTORIES here: if dst is * a directory, we can't replace it with a symlink. XXX In fact, * it's not clear what good this bit is. */ { int symlinkchange; if (delete_files) /* deleting from destination */ { if (verbose > 2) printf ("=== %s and %s are the same, deleting %s\n", src->name, dst->name, dst->name ); return 1; /* always delete if it's the same file */ } else /* linking */ { symlinkchange = (dst->type == ft_symlink) /* replace anyway if we have a symlink */ ^ ((create_symlinks & SYMLINK_FILES) != 0); /* and we want a hard link, or if we don't * have a symlink but we want one */ if (verbose > 2) { if (symlinkchange) { printf ("=== %s and %s point to the same file, relinking\n", src->name, dst->name); return 1; } else printf ("=== %s and %s are the same file\n", src->name, dst->name); /* FALLTHROUGH */ } } } else /* not the same file */ { if (src->stat.st_size == dst->stat.st_size) /* no: but same size */ { if (src->stat.st_mtime == dst->stat.st_mtime) /* and same time? */ { if (update_only) /* only newer stuff */ return 0; /* don't replace */ if ((want_diff || compare_files) /* we want to diff or compare, */ && docompare (src->name, dst->name) ) /* and they're the same */ { if (prompt) /* he still wants to be prompted */ { ls2 (src, dst); /* show both files */ if (delete_files) printf ("Remove %s? ", dst->name); else printf ("Replace %s with link to %s? ", dst->name, src->name); return query () == 'y'; } else { if (verbose > 2) printf ("=== %s and %s have the same contents\n", src->name, dst->name); return 1; } } else /* unconditionally */ return 1; } else /* files differ */ { if (verbose > 2) printf ("### %s and %s are the same size, but contents differ\n", src->name, dst->name); if (want_diff) /* want a diff, */ dodiff (src->name, dst->name, want_diff); /* do it */ if (keep_different) /* keep different files, */ return ! compare_files; /* they're the same: replace unconditionally */ else /* user needs to decide */ { ls2 (src, dst); /* show both files */ if (compare_files) /* they're not the same */ printf ("### Contents differ\n"); if (pretending) return 0; else { if (delete_files) printf ("Remove %s? ", dst->name); else printf ("Replace %s with link to %s? ", dst->name, src->name); switch (query ()) { case 'c': /* compare the two */ { char command [256]; sprintf (command, "cmp %s %s", src->name, dst->name); return ! system (command); /* the files are not the same */ } case 'Y': /* unconditionally replace */ case 'y': return 1; } } } } } /* The files are not the same size. Check if we want to replace with newer files */ else if (src->stat.st_mtime > dst->stat.st_mtime) /* is src newer? */ { /* yes */ if (verbose > 2) printf (">>> %s is newer than %s (different size)\n", src->name, dst->name); if (replace_newer > 1) /* replace unconditionally with newer version? */ return 1; else if (replace_newer) /* replace with newer version */ { if (want_diff) dodiff (src->name, dst->name, want_diff); /* do it */ ls2 (src, dst); /* show both files */ printf (">>> Source file is newer\n"); if (pretending) return 0; else { if (delete_files) printf ("Remove %s? ", dst->name); else printf ("Replace %s with link to %s? ", dst->name, src->name); return query () == 'y'; } } } /* Files are not the same size, and src is older or same age as dst-> * Only consider them if we asked for diffs */ else if (want_diff) /* older */ { dodiff (src->name, dst->name, want_diff); /* do it */ ls2 (src, dst); /* show both files */ if (src->stat.st_mtime < dst->stat.st_mtime) printf ("<<< Dest file is newer\n"); if (pretending) return 0; else { if (delete_files) printf ("Remove %s? ", dst->name); else printf ("Replace %s with link to %s? ", dst->name, src->name); return query () == 'y'; } } else /* different size, don't do it */ { if (verbose) fprintf (stderr, "<> %s and %s differ in size - ignored\n", src->name, dst->name); return 0; } } return 0; } /* * Check an individual file and perform what is needed. */ void check_file (char *filename, char *srcdirname, char *dstdirname ) { int status; /* status from system calls */ int linknamelen = 0; /* length of link name */ struct file_info src; struct file_info dst; if (strcmp (filename, ".") /* ignore current directory */ && strcmp (filename, "..") ) /* and parent */ { strcpy (src.name, srcdirname); /* get directory name */ strcat (src.name, "/"); /* slash */ strcat (src.name, filename); /* and file name */ strcpy (dst.name, dstdirname); /* get directory name */ strcat (dst.name, "/"); /* slash */ strcat (dst.name, filename); /* and file name */ src.linkloop = 0; /* until proven otherwise */ /* * 1. Establish what kind of source file we have. If we don't * like it, ignore it. If it's not there, complain if we're * verbose enough. */ if (lstat (src.name, &src.stat)) { if (verbose) fprintf (stderr, "*** Can't stat %s: %s\n", src.name, strerror (errno)); return; } if (S_ISLNK (src.stat.st_mode)) /* it's a symlink, */ { src.type = ft_symlink; /* note what it is */ src.linkstat = src.stat; /* and save the link status */ if ((status = stat (src.name, &src.stat))) { if ((errno == ENOENT) && symlink_to_broken) src.type = ft_broken_symlink; else { fprintf (stderr, "*** Can't stat %s: %s\n", src.name, strerror (errno)); return; } } if ((linknamelen = readlink (src.name, src.linkname, MAXPATHLEN)) < 0) { fprintf (stderr, "*** Can't get final name of symlink %s: %s\n", src.name, strerror (errno) ); return; } else { src.linkname [linknamelen] = '\0'; /* terminate string */ /* * If we're turning symlinks into real links, we have the * danger of cycles. Recognize that here and decide what to * do about it later. */ if ((linknamelen < strlen (src.name)) /* link name is shorter */ && S_ISDIR (src.stat.st_mode) /* and it's linking to a directory */ && ! (memcmp (src.linkname, /* and it's a subset, */ src.name, linknamelen)) ) src.linkloop = 1; /* note the fact for later */ } } else if (S_ISDIR (src.stat.st_mode)) /* it's a directory, */ src.type = ft_directory; else if (S_ISREG (src.stat.st_mode)) src.type = ft_file; else return; /* * 2. Establish what kind of destination file we have, if any. * If it doesn't exist, note the fact. If we don't like it, * complain. */ if (lstat (dst.name, &dst.stat)) { if (errno == ENOENT) { if (delete_files) /* nothing to do */ return; else dst.type = ft_none; } else { fprintf (stderr, "*** Can't stat %s: %s\n", dst.name, strerror (errno)); return; } } else if (S_ISLNK (dst.stat.st_mode)) /* it's a symlink, */ { dst.linkstat = dst.stat; /* and save the link status */ if ((status = stat (dst.name, &dst.stat))) { if (! delete_files) /* complain if we're not deleting */ fprintf (stderr, "*** Broken symlink %s: %s\n", dst.name, strerror (errno)); dst.type = ft_broken_symlink; } else dst.type = ft_symlink; /* note what it is */ if ((! status) /* we got the stat */ && (create_symlinks & SYMLINK_ORIGINAL) /* we want to link to the original file */ && ((status = readlink (dst.name, dst.linkname, MAXPATHLEN)) < 0) ) /* but we can't find it */ { fprintf (stderr, "*** Can't get final name of %s: %s\n", dst.name, strerror (errno)); return; } else dst.linkname [status] = '\0'; /* terminate string */ } else if (S_ISDIR (dst.stat.st_mode)) /* it's a directory, */ dst.type = ft_directory; else if (S_ISREG (dst.stat.st_mode)) dst.type = ft_file; else return; /* 3. Based on the source type, decide what to do */ switch (src.type) { case ft_none: /* to make the compiler happy */ break; case ft_symlink: /* source is a symlink */ if (S_ISDIR (src.stat.st_mode)) /* it's really a directory */ { switch (dst.type) /* what kind of destination do we have? */ { case ft_broken_symlink: if (verbose > 1) printf ("--- Remove broken symlink %s\n", dst.name); if ((! pretending) && unlink (dst.name)) { printf ("*** Couldn't remove broken symlink %s: %s\n", dst.name, strerror (errno)); return; } /* fall through */ case ft_none: /* no destination file */ if (delete_files) /* nothing to do if we're deleting, */ return; /* * Even if we have specified not to symlink directories, we * must do so if the link name is a subset of the link name; * otherwise we'll loop making longer and longer pathnames. */ if (linknew /* create links if not there */ && makedirs /* create dirs */ && ((create_symlinks & SYMLINK_DIRECTORIES) /* and make symlinks */ || (! memcmp (src.name, src.linkname, linknamelen)) ) ) dolink (&src, &dst, 1); /* create a link */ else checkdir (&src, &dst); /* create a directory */ break; case ft_symlink: /* symlink */ /* * This will only work if the symlink points to the * destination directory, and it's only interesting if we * want to replace it with a directory */ if ((src.stat.st_dev == dst.stat.st_dev) /* same device */ && (src.stat.st_ino == dst.stat.st_ino) ) /* they're the same directory, */ { if (delete_files) { if (verbose > 1) printf ("--- Remove directory symlink %s\n", dst.name); if (! pretending) { if (unlink (dst.name)) printf ("*** Couldn't remove %s: %s\n", dst.name, strerror (errno)); } } else if ((! (create_symlinks & SYMLINK_DIRECTORIES) ) /* don't symlink to the directory */ && check_replace (&src, &dst)) /* and we can replace it */ { if (pretending) /* just pretending */ printf ("--- Remove directory symlink %s\n", dst.name); else if (unlink (dst.name)) printf ("*** Couldn't remove %s: %s\n", dst.name, strerror (errno)); else checkdir (&src, &dst); /* create a new directory */ } } break; case ft_file: /* can't replace a directory with a file */ printf ("*** Can't link file %s to directory %s\n", dst.name, src.name); break; case ft_directory: /* directory exists already: do nothing */ break; } break; } /* FALLTHROUGH if the symlink isn't a directory */ case ft_file: /* source is a regular file */ switch (dst.type) /* what kind of destination do we have? */ { case ft_broken_symlink: if (delete_files) { if (verbose > 1) printf ("--- Remove %s\n", dst.name); if (! pretending) { if (unlink (dst.name)) printf ("*** Couldn't remove %s: %s\n", dst.name, strerror (errno)); } } else dolink (&src, &dst, create_symlinks & SYMLINK_FILES); /* create a new link */ break; case ft_none: /* no destination file */ if (delete_files) return; if (linknew) dolink (&src, &dst, create_symlinks & SYMLINK_FILES); /* create a new link */ break; case ft_file: case ft_symlink: /* symlink */ if (check_replace (&src, &dst)) { if (delete_files) { if (verbose > 1) printf ("--- Remove %s\n", dst.name); if (! pretending) { if (unlink (dst.name)) printf ("*** Couldn't remove %s: %s\n", dst.name, strerror (errno)); } } else dolink (&src, &dst, create_symlinks & SYMLINK_FILES); /* create a new link */ } break; case ft_directory: /* can't replace a directory with a file */ printf ("*** Can't link directory %s to file %s\n", dst.name, src.name); break; } break; case ft_broken_symlink: /* source is broken symlink */ if (delete_files && (dst.type == ft_broken_symlink)) { if (verbose > 1) printf ("--- Remove broken symlink %s\n", dst.name); if (! pretending) { if (unlink (dst.name)) printf ("*** Couldn't remove broken symlink %s: %s\n", dst.name, strerror (errno)); } } else printf ("%s is a broken symlink, no action taken\n", src.name); break; case ft_directory: /* source is a directory */ switch (dst.type) /* what kind of destination do we have? */ { case ft_broken_symlink: if (delete_files) { if (verbose > 1) printf ("--- Remove %s\n", dst.name); if (! pretending) { if (unlink (dst.name)) printf ("*** Couldn't remove %s: %s\n", dst.name, strerror (errno)); } } /* else FALLTHROUGH */ case ft_none: /* no destination file */ if (delete_files) return; if (linknew) { if ((create_symlinks & SYMLINK_DIRECTORIES) && makedirs ) /* symlink to the directory */ dolink (&src, &dst, 1); /* create a link */ else checkdir (&src, &dst); /* create a directory */ } /* Don't do anything if we don't want new files and directories created */ break; case ft_symlink: /* symlink */ /* * This will only work if the symlink points to the * destination directory, and it's only interesting if we want * to replace it with a directory. */ if ((src.stat.st_dev == dst.stat.st_dev) /* same device */ && (src.stat.st_ino == dst.stat.st_ino) /* they're the same directory, */ && ! (create_symlinks & SYMLINK_DIRECTORIES) ) /* don't symlink to the directory */ if (check_replace (&src, &dst)) { if (pretending) /* just pretending */ printf ("--- Remove directory symlink %s\n", dst.name); else if (unlink (dst.name)) printf ("*** Couldn't remove %s: %s\n", dst.name, strerror (errno)); else checkdir (&src, &dst); /* create a new directory */ } break; case ft_file: /* can't replace a directory with a file */ if (delete_files) printf ("Source %s is a directory, dest %s is a file--ignoring\n", src.name, dst.name ); else printf ("*** Can't link file %s to directory %s\n", dst.name, src.name); break; case ft_directory: checkdir (&src, &dst); /* check the contents of the directory */ break; } break; } } } /* Go through the directories and decide what to do */ void checkdir (struct file_info *src, struct file_info *dst) { struct dirent *file; struct stat srcdirstat; /* stat of src directory */ struct stat dstdirstat; /* stat of dest directory */ char srcname [MAXPATHLEN]; char dstname [MAXPATHLEN]; DIR *srcdir; /* open source directory */ strcpy (srcname, src->name); strcpy (dstname, dst->name); if (stat (srcname, &srcdirstat)) /* stat source file */ { printf ("*** Can't access source directory %s: %s\n", srcname, strerror (errno)); return; } if (stat (dstname, &dstdirstat)) /* can't access directory? */ { if (errno == ENOENT) /* sure: it doesn't exist */ { if (delete_files) /* that's what we wanted, */ return; /* do nothing */ if (pretending) { if (makedirs) printf ("+++ Create directory %s\n", dstname); else { printf (" Don't create directory %s\n", dstname); return; } } /* * There's no way to set makedirs, only to reset it, which we do * when we set file deletion. Make sure it stays that way. */ else if (makedirs) /* we want to make new ones */ { if (mkdir (dstname, srcdirstat.st_mode & 07777)) /* same permissions as source dir */ { printf ("*** Can't create %s: %s\n", dstname, strerror (errno)); Exit (1); } if (verbose) printf ("+++ Created directory %s\n", dstname); } else { if (verbose) /* don't want to make directories */ printf (" Don't create directory %s\n", dstname); return; /* nothing to do here */ } } else /* directory exists, but we have trouble */ { printf ("*** Can't stat %s: %s\n", dstname, strerror (errno)); return; } } /* * Source and destination directories both exist. Look at them. */ if (verbose > 3) printf ("--- Comparing directories %s and %s\n", src->name, dst->name ); if (! (srcdir = opendir (srcname))) { fprintf (stderr, "*** Couldn't open %s: %s\n", srcname, strerror (errno)); Exit (3); } /* go through the source directory */ while ((file = readdir (srcdir))) check_file (file->d_name, srcname, dstname); /* * Delete the directory if it's empty. We don't check: unlink will * do it for us, and it's easier like that. */ if (delete_dirs) delete_directory (dst); closedir (srcdir); } void usage (char *name) { printf ("Usage:\n\n\t%s clone \n" "\t Make a copy of tree at \n\n", name); printf ("\t%s merge \n" "\t Replace all identical files in with a link to the file in \n\n", name); printf ("\t%s rm \n" "\t Remove all files in which exist in \n\n", name); printf ("\t%s update \n" "\t Update files in with links to files in \n\n" "\tNote that must come after the keyword\n\n", name); printf ("\t%s \n" "\t manipulate links in detail\n\n", name); printf ("Replace all identical files in hierarchy with a link\n" "to the file in . The hierarchy is created if it\n" "does not exist.\n\n" "Flags can be:\n" "\t-c:\tDon't compare files with different timestamps before prompting\n" "\t-d:\tDo a diff | less to screen before prompting for replacement\n" "\t\t(see also -w)\n" "\t-D:\tDon't create directories if they're not present\n" "\t-k:\tKeep files with different contents\n" "\t-n:\tPrompt to replace files with newer versions\n" "\t-N:\tReplace files with newer versions unconditionally\n" "\t-q:\tPrompt before linking files with same timestamps.\n" "\t-r:\tRemove corresponding files\n" "\t-R:\tRemove corresponding files and empty directories\n" "\t-s:\tCreate symlinks (bitmapped flag):\n" "\t\t 1: create symlink if old file was a symlink.\n" "\t\t 2: create symlink if old file was a regular file.\n" "\t\t 4: create symlink if old file was a directory.\n" "\t\t 8: link to original source file, not a source symlink.\n" "\t-v, ( is between 0 and 2): specify verbosity.\n" "\t-V:\tPrint version information and exit.\n" "\t-w:\tDo a diff -w | less to screen before prompting for replacement\n" "\t-X:\tDon't create a link if the destination file does not exist\n" "\t-z: Display the actions, but don't perform them\n" ); exit (1); } int main (int argc, char *argv []) { int show_usage = 0; /* set to display usage message */ int i; for (i = 1; i < argc; i++) { if (argv [i] [0] == '-') /* option */ switch (argv [i] [1]) { case 'c': compare_files = 0; /* don't compare files before prompting */ break; case 'd': /* do a diff */ want_diff = 1; break; case 'D': /* no automatic directories */ makedirs = 0; break; case 'F': /* no automatic files */ makefiles = 0; break; case 'k': /* keep different files */ keep_different = 1; break; case 'n': /* replace with newer version? */ replace_newer = 1; /* yes */ break; case 'N': /* replace with newer version? */ replace_newer = 2; /* yes, unconditionally */ break; case 'q': /* prompt anyway */ prompt = 1; break; case 'r': delete_files = 1; makedirs = 0; break; case 'R': delete_files = 1; delete_dirs = 1; makedirs = 0; break; case 's': if (! (create_symlinks = atoi (&argv [i] [2]))) /* 1: create symlinks if old was a symlink */ /* 2: create symlinks unconditionally */ create_symlinks = 1; /* default to 1 */ break; case 'u': update_only = 1; /* don't replace files with same content */ break; case 'v': if (argv [i] [2]) /* arg supplied */ verbose = atoi(&argv [i] [2]); else verbose = 1; break; case 'V': printf ("$Id: mklinks.c,v 1.9 2005/07/16 03:08:42 grog Exp grog $\n"); return 0; case 'w': /* do a diff -w */ want_diff = 2; /* 2 means -w (intuitive, huh?) */ break; case 'X': linknew = 0; /* don't create links for missing files */ break; case 'z': pretending = 1; /* just show what we'd do */ break; default: printf ("Unknown flag: %s\n", argv [i]); show_usage = 1; } else if (i < (argc - 2)) /* macro name */ { if (! strcasecmp (argv [i], "update")) /* update dest tree */ { compare_files = 0; /* don't compare files before prompting */ want_diff = 0; /* don't diff */ makedirs = 0; /* don't create directories */ makefiles = 0; /* don't create files */ keep_different = 1; /* don't replace different files */ replace_newer = 2; /* unless they're newer, then unconditionally */ prompt = 1; /* prompt for changes */ create_symlinks = 1; /* create symlinks if old was symlink */ want_diff = 2; /* diff -w */ linknew = 1; /* create links for files that don't exist */ update_only = 1; /* don't replace identical files */ } else if (! strcasecmp (argv [i], "clone")) /* clone new tree */ { compare_files = 0; /* don't compare files before prompting */ want_diff = 0; /* no diffs, thanks */ makedirs = 1; /* create directories */ makefiles = 1; /* and files */ keep_different = 1; /* don't replace different files */ replace_newer = 2; /* unless they're newer, then unconditionally */ prompt = 0; create_symlinks = 0; /* no symlinks */ want_diff = 0; /* ask no questions */ linknew = 1; /* create links for files that don't exist */ } #if 0 /* XXX Think about this */ else if (! strcasecmp (argv [i], "perms")) /* set permissions */ { compare_files = 0; /* don't compare files before prompting */ want_diff = 0; /* no diffs, thanks */ makedirs = 0; /* create directories */ makefiles = 0; /* and files */ keep_different = 0; /* don't replace different files */ replace_newer = ; /* unless they're newer, then unconditionally */ prompt = 0; create_symlinks = 0; /* no symlinks */ want_diff = 0; /* ask no questions */ linknew = 1; /* create links for files that don't exist */ } #endif else if (! strcasecmp (argv [i], "merge")) /* * merge two trees. For each identical file in basedst which is * also in basesrc, remove the file in basedst and replace with a link * (normal link where possible) to the file in basesrc. */ { compare_files = 0; /* don't compare files */ makedirs = 0; /* don't create directories */ makefiles = 0; /* don't create files */ keep_different = 1; /* don't replace different files */ replace_newer = 0; /* don't update files */ prompt = 0; /* be quiet */ create_symlinks = 0; /* don't create symlinks unless we have to */ want_diff = 0; /* ask no questions */ linknew = 0; /* don't create links for missing files */ } else if (! strcasecmp (argv [i], "rm")) /* remove files in dest tree */ { compare_files = 0; /* don't compare files before prompting */ delete_files = 1; /* delete them */ want_diff = 0; makedirs = 0; makefiles = 1; keep_different = 1; replace_newer = 0; /* don't replace different files */ prompt = 0; create_symlinks = 0; /* 1 */ want_diff = 0; /* ask no questions */ linknew = 1; /* create links for files that don't exist */ } else /* unknown macro name */ { fprintf (stderr, "Unknown command %s\n", argv [i]); usage (argv [0]); } } else if (basedst.name [0]) printf ("Too many arguments: %s\n", argv [i]); else if (basesrc.name [0]) strlcpy (basedst.name, absname (argv [i]), MAXPATHLEN); else strlcpy (basesrc.name, absname (argv [i]), MAXPATHLEN); } if (show_usage || (! basedst.name [0])) usage (argv [0]); if ((interactive = isatty (Stdin)) /* interactive */ && tcgetattr (Stdin, ¤t_status) ) /* but can't get attributes? */ { perror ("Can't get stdin attributes"); /* shouldn't be able to happen */ exit (1); } noicanon_status = current_status; /* make a copy */ noicanon_status.c_lflag &= ~ICANON; /* and turn icanon off */ noicanon_status.c_cc [VMIN] = 1; /* 1 character at a time */ if (interactive && tcsetattr (Stdin, TCSANOW, &noicanon_status)) /* can't set attributes? */ { perror ("Can't set stdin attributes"); /* shouldn't be able to happen */ exit (1); } if (stat (basesrc.name, &basesrc.stat)) { fprintf (stderr, "Can't stat %s: %s (%d)\n", basesrc.name, strerror (errno), errno); return 1; } if (stat (basedst.name, &basedst.stat)) { if (errno == ENOENT) /* destination directory doesn't exist */ { if (delete_files) { fprintf (stderr, "Destination directory %s doesn't exist, nothing to do.\n", basedst.name); Exit (0); } /* * If destdir doesn't exist, and we're not deleting, we'll * probably create it. Fall through. */ } else if (mkdir (basedst.name, basesrc.stat.st_mode)) { fprintf (stderr, "Can't create %s: %s (%d)\n", basedst.name, strerror (errno), errno); return 1; } else { if (stat (basedst.name, &basedst.stat)) { fprintf (stderr, "Can't stat %s: %s (%d)\n", basedst.name, strerror (errno), errno); return 1; } samefs = basesrc.stat.st_dev == basedst.stat.st_dev; /* same file system? */ } } checkdir (&basesrc, &basedst); Exit (0); /* reset term status and exit */ return 0; /* to keep compiler happy */ }