root/usr/src/cmd/filesync/rename.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1995 Sun Microsystems, Inc.  All Rights Reserved
 *
 * module:
 *      rename.c
 *
 * purpose:
 *      routines to determine whether or not any renames have taken place
 *      and note them (for reconciliation) if we find any
 *
 * contents:
 *      find_renames . look for files that have been renamed
 *      find_oldname . (static) find the file we were renamed from
 *      note_rename .. (static) note the rename for subsequent reconciliation
 *
 * notes:
 *      the reason renames warrant special attention is because the tree
 *      we have constructed is name based, and a directory rename can
 *      appear as zillions of changes.  We attempt to find and deal with
 *      renames prior to doing the difference analysis.
 *
 *      The only case we deal with here is simple renames.  If new links
 *      have been created beneath other directories (i.e. a file has been
 *      moved from one directory to another), the generalized link finding
 *      stuff will deal with it.
 *
 *      This is still under construction, and to completely deal with
 *      directory renames may require some non-trivial tree restructuring.
 *      There is a whole design note on this subject.  In the mean time,
 *      we still detect file renames, so that the user will see them
 *      reported as "mv"s rather than as "ln"s and "rm"s.  Until directory
 *      renames are fully implemented, they will instead be handled as
 *      mkdirs, massive links and unlinks, and rmdirs.
 */
#ident  "%W%    %E% SMI"

#include <stdio.h>

#include "filesync.h"
#include "database.h"


/* local routines */
static struct file *find_oldname(struct file *, struct file *, side_t);
static errmask_t
        note_rename(struct file *, struct file *, struct file *, side_t);

/*
 * routine:
 *      find_renames
 *
 * purpose:
 *      recursively perform rename analysis on a directory
 *
 * parameters:
 *      file node for the suspected directory
 *
 * returns:
 *      error mask
 *
 * note:
 *      the basic algorithm here is to search every directory
 *      for files that have been newly created on one side,
 *      and then look to see if they correspond to an identical
 *      file that has been newly deleted on the same side.
 */
errmask_t
find_renames(struct file *fp)
{       struct file *np, *rp;
        errmask_t errs = 0;
        int stype, dtype, btype, side;

        /* if this isn't a directory, there is nothing to analyze       */
        if (fp->f_files == 0)
                return (0);

        /* look for any files under this directory that may have been renamed */
        for (np = fp->f_files; np; np = np->f_next) {
                btype = np->f_info[OPT_BASE].f_type;
                stype = np->f_info[OPT_SRC].f_type;
                dtype = np->f_info[OPT_DST].f_type;

                /* a rename must be a file that is new on only one side */
                if (btype == 0 && stype != dtype && (!stype || !dtype)) {
                        side = stype ? OPT_SRC : OPT_DST;
                        rp = find_oldname(fp, np, side);
                        if (rp)
                                errs |= note_rename(fp, np, rp, side);
                }
        }

        /* recursively examine all my children                  */
        for (np = fp->f_files; np; np = np->f_next) {
                errs |= find_renames(np);
        }

        return (errs);
}

/*
 * routine:
 *      find_oldname
 *
 * purpose:
 *      to search for an old name for a newly discovered file
 *
 * parameters:
 *      file node for the containing directory
 *      file node for the new file
 *      which side the rename is believed to have happened on
 *
 * returns:
 *      pointer to likely previous file
 *      0       no candidate found
 *
 * note:
 *      this routine only deals with simple renames within a single
 *      directory.
 */
static struct file *find_oldname(struct file *dirp, struct file *new,
        side_t side)
{       struct file *fp;
        long maj, min;
        ino_t inum;
        off_t size;
        side_t otherside = (side == OPT_SRC) ? OPT_DST : OPT_SRC;

        /* figure out what we're looking for            */
        inum = new->f_info[side].f_ino;
        maj  = new->f_info[side].f_d_maj;
        min  = new->f_info[side].f_d_min;
        size = new->f_info[side].f_size;

        /*
         * search the same directory for any entry that might describe
         * the previous name of the new file.
         */
        for (fp = dirp->f_files; fp; fp = fp->f_next) {
                /* previous name on changed side must no longer exist   */
                if (fp->f_info[side].f_type != 0)
                        continue;

                /* previous name on the other side must still exist     */
                if (fp->f_info[otherside].f_type == 0)
                        continue;

                /* it must describe the same inode as the new file      */
                if (fp->f_info[OPT_BASE].f_type != new->f_info[side].f_type)
                        continue;       /* must be same type            */
                if (((side == OPT_SRC) ? fp->f_s_inum : fp->f_d_inum) != inum)
                        continue;       /* must be same inode #         */
                if (((side == OPT_SRC) ? fp->f_s_maj : fp->f_d_maj) != maj)
                        continue;       /* must be same major #         */
                if (((side == OPT_SRC) ? fp->f_s_min : fp->f_d_min) != min)
                        continue;       /* must be same minor #         */

                /*
                 * occasionally a prompt delete and create can reuse the
                 * same i-node in the same directory.  What we really
                 * want is generation, but that isn't available just
                 * yet, so our poor-man's approximation is the size.
                 * There is little point in checking ownership and
                 * modes, since the fact that it is in the same
                 * directory strongly suggests that it is the same
                 * user who is doing the deleting and creating.
                 */
                if (fp->f_info[OPT_BASE].f_size != size)
                        continue;

                /* looks like we found a match                          */
                return (fp);
        }

        /* no joy       */
        return (0);
}

/*
 * routine:
 *      note_rename
 *
 * purpose:
 *      to record a discovered rename, so that the reconciliation
 *      phase will deal with it as a rename rather than as link
 *      followed by an unlink.
 *
 * parameters:
 *      file node for the containing directory
 *      file node for the new file
 *      file node for the old file
 *      which side the rename is believed to have happened on
 *
 * returns:
 *      error mask
 */
static errmask_t
note_rename(struct file *dirp, struct file *new,
                        struct file *old, side_t side)
{
        int dir;
        errmask_t errs = 0;
        static char *sidenames[] = {"base", "source", "dest"};

        dir = new->f_info[side].f_type == S_IFDIR;

        if (opt_debug & DBG_ANAL)
                fprintf(stderr, "ANAL: NOTE RENAME %s %s/%s -> %s/%s on %s\n",
                        dir ? "directory" : "file",
                        dirp->f_name, old->f_name, dirp->f_name, new->f_name,
                        sidenames[side]);

        /* FIX: we don't deal with directory renames yet        */
        if (dir)
                return (0);

        /* note that a rename has taken place                   */
        if (side == OPT_SRC) {
                new->f_srcdiffs |= D_RENAME_TO;
                old->f_srcdiffs |= D_RENAME_FROM;
        } else {
                new->f_dstdiffs |= D_RENAME_TO;
                old->f_dstdiffs |= D_RENAME_FROM;
        }

        /* put a link to the old name in the new name           */
        new->f_previous = old;

        /* for most files, there is nothing else we have to do  */
        if (!dir)
                return (errs);

        /*
         * FIX ... someday we are going to have to merge the old and
         *         new children into a single tree, but there are
         *         horrendous backout problems if we are unable to
         *         do the mvdir, so I have postponed this feature.
         */

        return (errs);
}