1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 /*
  23  * Copyright (c) 1995 Sun Microsystems, Inc.  All Rights Reserved
  24  *
  25  * module:
  26  *      rename.c
  27  *
  28  * purpose:
  29  *      routines to determine whether or not any renames have taken place
  30  *      and note them (for reconciliation) if we find any
  31  *
  32  * contents:
  33  *      find_renames . look for files that have been renamed
  34  *      find_oldname . (static) find the file we were renamed from
  35  *      note_rename .. (static) note the rename for subsequent reconciliation
  36  *
  37  * notes:
  38  *      the reason renames warrant special attention is because the tree
  39  *      we have constructed is name based, and a directory rename can
  40  *      appear as zillions of changes.  We attempt to find and deal with
  41  *      renames prior to doing the difference analysis.
  42  *
  43  *      The only case we deal with here is simple renames.  If new links
  44  *      have been created beneath other directories (i.e. a file has been
  45  *      moved from one directory to another), the generalized link finding
  46  *      stuff will deal with it.
  47  *
  48  *      This is still under construction, and to completely deal with
  49  *      directory renames may require some non-trivial tree restructuring.
  50  *      There is a whole design note on this subject.  In the mean time,
  51  *      we still detect file renames, so that the user will see them
  52  *      reported as "mv"s rather than as "ln"s and "rm"s.  Until directory
  53  *      renames are fully implemented, they will instead be handled as
  54  *      mkdirs, massive links and unlinks, and rmdirs.
  55  */
  56 #ident  "%W%    %E% SMI"
  57 
  58 #include <stdio.h>
  59 
  60 #include "filesync.h"
  61 #include "database.h"
  62 
  63 
  64 /* local routines */
  65 static struct file *find_oldname(struct file *, struct file *, side_t);
  66 static errmask_t
  67         note_rename(struct file *, struct file *, struct file *, side_t);
  68 
  69 /*
  70  * routine:
  71  *      find_renames
  72  *
  73  * purpose:
  74  *      recursively perform rename analysis on a directory
  75  *
  76  * parameters:
  77  *      file node for the suspected directory
  78  *
  79  * returns:
  80  *      error mask
  81  *
  82  * note:
  83  *      the basic algorithm here is to search every directory
  84  *      for files that have been newly created on one side,
  85  *      and then look to see if they correspond to an identical
  86  *      file that has been newly deleted on the same side.
  87  */
  88 errmask_t
  89 find_renames(struct file *fp)
  90 {       struct file *np, *rp;
  91         errmask_t errs = 0;
  92         int stype, dtype, btype, side;
  93 
  94         /* if this isn't a directory, there is nothing to analyze       */
  95         if (fp->f_files == 0)
  96                 return (0);
  97 
  98         /* look for any files under this directory that may have been renamed */
  99         for (np = fp->f_files; np; np = np->f_next) {
 100                 btype = np->f_info[OPT_BASE].f_type;
 101                 stype = np->f_info[OPT_SRC].f_type;
 102                 dtype = np->f_info[OPT_DST].f_type;
 103 
 104                 /* a rename must be a file that is new on only one side */
 105                 if (btype == 0 && stype != dtype && (!stype || !dtype)) {
 106                         side = stype ? OPT_SRC : OPT_DST;
 107                         rp = find_oldname(fp, np, side);
 108                         if (rp)
 109                                 errs |= note_rename(fp, np, rp, side);
 110                 }
 111         }
 112 
 113         /* recursively examine all my children                  */
 114         for (np = fp->f_files; np; np = np->f_next) {
 115                 errs |= find_renames(np);
 116         }
 117 
 118         return (errs);
 119 }
 120 
 121 /*
 122  * routine:
 123  *      find_oldname
 124  *
 125  * purpose:
 126  *      to search for an old name for a newly discovered file
 127  *
 128  * parameters:
 129  *      file node for the containing directory
 130  *      file node for the new file
 131  *      which side the rename is believed to have happened on
 132  *
 133  * returns:
 134  *      pointer to likely previous file
 135  *      0       no candidate found
 136  *
 137  * note:
 138  *      this routine only deals with simple renames within a single
 139  *      directory.
 140  */
 141 static struct file *find_oldname(struct file *dirp, struct file *new,
 142         side_t side)
 143 {       struct file *fp;
 144         long maj, min;
 145         ino_t inum;
 146         off_t size;
 147         side_t otherside = (side == OPT_SRC) ? OPT_DST : OPT_SRC;
 148 
 149         /* figure out what we're looking for            */
 150         inum = new->f_info[side].f_ino;
 151         maj  = new->f_info[side].f_d_maj;
 152         min  = new->f_info[side].f_d_min;
 153         size = new->f_info[side].f_size;
 154 
 155         /*
 156          * search the same directory for any entry that might describe
 157          * the previous name of the new file.
 158          */
 159         for (fp = dirp->f_files; fp; fp = fp->f_next) {
 160                 /* previous name on changed side must no longer exist   */
 161                 if (fp->f_info[side].f_type != 0)
 162                         continue;
 163 
 164                 /* previous name on the other side must still exist     */
 165                 if (fp->f_info[otherside].f_type == 0)
 166                         continue;
 167 
 168                 /* it must describe the same inode as the new file      */
 169                 if (fp->f_info[OPT_BASE].f_type != new->f_info[side].f_type)
 170                         continue;       /* must be same type            */
 171                 if (((side == OPT_SRC) ? fp->f_s_inum : fp->f_d_inum) != inum)
 172                         continue;       /* must be same inode #         */
 173                 if (((side == OPT_SRC) ? fp->f_s_maj : fp->f_d_maj) != maj)
 174                         continue;       /* must be same major #         */
 175                 if (((side == OPT_SRC) ? fp->f_s_min : fp->f_d_min) != min)
 176                         continue;       /* must be same minor #         */
 177 
 178                 /*
 179                  * occasionally a prompt delete and create can reuse the
 180                  * same i-node in the same directory.  What we really
 181                  * want is generation, but that isn't available just
 182                  * yet, so our poor-man's approximation is the size.
 183                  * There is little point in checking ownership and
 184                  * modes, since the fact that it is in the same
 185                  * directory strongly suggests that it is the same
 186                  * user who is doing the deleting and creating.
 187                  */
 188                 if (fp->f_info[OPT_BASE].f_size != size)
 189                         continue;
 190 
 191                 /* looks like we found a match                          */
 192                 return (fp);
 193         }
 194 
 195         /* no joy       */
 196         return (0);
 197 }
 198 
 199 /*
 200  * routine:
 201  *      note_rename
 202  *
 203  * purpose:
 204  *      to record a discovered rename, so that the reconciliation
 205  *      phase will deal with it as a rename rather than as link
 206  *      followed by an unlink.
 207  *
 208  * parameters:
 209  *      file node for the containing directory
 210  *      file node for the new file
 211  *      file node for the old file
 212  *      which side the rename is believed to have happened on
 213  *
 214  * returns:
 215  *      error mask
 216  */
 217 static errmask_t
 218 note_rename(struct file *dirp, struct file *new,
 219                         struct file *old, side_t side)
 220 {
 221         int dir;
 222         errmask_t errs = 0;
 223         static char *sidenames[] = {"base", "source", "dest"};
 224 
 225         dir = new->f_info[side].f_type == S_IFDIR;
 226 
 227         if (opt_debug & DBG_ANAL)
 228                 fprintf(stderr, "ANAL: NOTE RENAME %s %s/%s -> %s/%s on %s\n",
 229                         dir ? "directory" : "file",
 230                         dirp->f_name, old->f_name, dirp->f_name, new->f_name,
 231                         sidenames[side]);
 232 
 233         /* FIX: we don't deal with directory renames yet        */
 234         if (dir)
 235                 return (0);
 236 
 237         /* note that a rename has taken place                   */
 238         if (side == OPT_SRC) {
 239                 new->f_srcdiffs |= D_RENAME_TO;
 240                 old->f_srcdiffs |= D_RENAME_FROM;
 241         } else {
 242                 new->f_dstdiffs |= D_RENAME_TO;
 243                 old->f_dstdiffs |= D_RENAME_FROM;
 244         }
 245 
 246         /* put a link to the old name in the new name           */
 247         new->f_previous = old;
 248 
 249         /* for most files, there is nothing else we have to do  */
 250         if (!dir)
 251                 return (errs);
 252 
 253         /*
 254          * FIX ... someday we are going to have to merge the old and
 255          *         new children into a single tree, but there are
 256          *         horrendous backout problems if we are unable to
 257          *         do the mvdir, so I have postponed this feature.
 258          */
 259 
 260         return (errs);
 261 }