vos-move-copy-improvements-20040108
authorJeffrey Hutzelman <jhutz@cmu.edu>
Thu, 8 Jan 2004 21:54:10 +0000 (21:54 +0000)
committerDerrick Brashear <shadow@dementia.org>
Thu, 8 Jan 2004 21:54:10 +0000 (21:54 +0000)
FIXES 2815

add switches to vos move and vos copy. add vos clone.

src/volser/volser.p.h
src/volser/vos.c
src/volser/vsprocs.c

index 03af749..091b523 100644 (file)
@@ -156,9 +156,13 @@ struct partList {          /*used by the backup system */
 #define        VOK 0x02
 
 /* Values for the UV_RestoreVolume flags parameter */
+/* Also used for UV_CopyVolume and UV_CloneVolume */
 #define RV_FULLRST 0x1
 #define RV_OFFLINE 0x2
 #define RV_RDONLY  0x10000
+#define RV_CPINCR  0x20000
+#define RV_NOVLDB  0x40000
+#define RV_NOCLONE 0x80000
 
 extern afs_uint32 vsu_GetVolumeID(char *astring, struct ubik_client *acstruct, afs_int32 *errp);
 extern int vsu_ExtractName(char rname[], char name[]);
index 93a0dd5..9015b50 100644 (file)
@@ -1789,7 +1789,8 @@ MoveVolume(as)
      register struct cmd_syndesc *as;
 {
 
-    afs_int32 volid, fromserver, toserver, frompart, topart, code, err;
+    afs_int32 volid, fromserver, toserver, frompart, topart;
+    afs_int32 flags, code, err;
     char fromPartName[10], toPartName[10];
 
     struct diskPartition partition;    /* for space check */
@@ -1847,6 +1848,9 @@ MoveVolume(as)
        return ENOENT;
     }
 
+    flags = 0;
+    if (as->parms[5].items) flags |= RV_NOCLONE;
+
     /*
      * check source partition for space to clone volume
      */
@@ -1894,7 +1898,8 @@ MoveVolume(as)
 
     /* successful move still not guaranteed but shoot for it */
 
-    code = UV_MoveVolume(volid, fromserver, frompart, toserver, topart);
+    code =
+       UV_MoveVolume2(volid, fromserver, frompart, toserver, topart, flags);
     if (code) {
        PrintDiagnostics("move", code);
        return code;
@@ -1912,7 +1917,7 @@ static
 CopyVolume(as)
      register struct cmd_syndesc *as;
 {
-    afs_int32 volid, fromserver, toserver, frompart, topart, code, err;
+    afs_int32 volid, fromserver, toserver, frompart, topart, code, err, flags;
     char fromPartName[10], toPartName[10], *tovolume;
     struct nvldbentry entry;
     struct diskPartition partition;    /* for space check */
@@ -1937,7 +1942,7 @@ CopyVolume(as)
     toserver = GetServer(as->parms[4].items->data);
     if (toserver == 0) {
        fprintf(STDERR, "vos: server '%s' not found in host table\n",
-               as->parms[3].items->data);
+               as->parms[4].items->data);
        return ENOENT;
     }
 
@@ -1985,7 +1990,7 @@ CopyVolume(as)
     topart = volutil_GetPartitionID(as->parms[5].items->data);
     if (topart < 0) {
        fprintf(STDERR, "vos: could not interpret partition name '%s'\n",
-               as->parms[4].items->data);
+               as->parms[5].items->data);
        return EINVAL;
     }
     if (!IsPartValid(topart, toserver, &code)) {       /*check for validity of the partition */
@@ -1994,13 +1999,14 @@ CopyVolume(as)
        else
            fprintf(STDERR,
                    "vos : partition %s does not exist on the server\n",
-                   as->parms[4].items->data);
+                   as->parms[5].items->data);
        return ENOENT;
     }
 
-    /*
-     * check source partition for space to clone volume
-     */
+    flags = 0;
+    if (as->parms[6].items) flags |= RV_OFFLINE;
+    if (as->parms[7].items) flags |= RV_RDONLY;
+    if (as->parms[8].items) flags |= RV_NOCLONE;
 
     MapPartIdIntoName(topart, toPartName);
     MapPartIdIntoName(frompart, fromPartName);
@@ -2039,8 +2045,8 @@ CopyVolume(as)
     /* successful copy still not guaranteed but shoot for it */
 
     code =
-       UV_CopyVolume(volid, fromserver, frompart, tovolume, toserver,
-                     topart);
+       UV_CopyVolume2(volid, fromserver, frompart, tovolume, toserver,
+                      topart, 0, flags);
     if (code) {
        PrintDiagnostics("copy", code);
        return code;
@@ -2056,6 +2062,311 @@ CopyVolume(as)
 
 
 static
+ShadowVolume(as)
+     register struct cmd_syndesc *as;
+{
+    afs_int32 volid, fromserver, toserver, frompart, topart, tovolid;
+    afs_int32 code, err, flags;
+    char fromPartName[10], toPartName[10], toVolName[32], *tovolume;
+    struct nvldbentry entry;
+    struct diskPartition partition;    /* for space check */
+    volintInfo *p, *q;
+
+    p = (volintInfo *) 0;
+    q = (volintInfo *) 0;
+
+    volid = vsu_GetVolumeID(as->parms[0].items->data, cstruct, &err);
+    if (volid == 0) {
+       if (err)
+           PrintError("", err);
+       else
+           fprintf(STDERR, "vos: can't find volume ID or name '%s'\n",
+                   as->parms[0].items->data);
+       return ENOENT;
+    }
+    fromserver = GetServer(as->parms[1].items->data);
+    if (fromserver == 0) {
+       fprintf(STDERR, "vos: server '%s' not found in host table\n",
+               as->parms[1].items->data);
+       return ENOENT;
+    }
+
+    toserver = GetServer(as->parms[3].items->data);
+    if (toserver == 0) {
+       fprintf(STDERR, "vos: server '%s' not found in host table\n",
+               as->parms[3].items->data);
+       return ENOENT;
+    }
+
+    frompart = volutil_GetPartitionID(as->parms[2].items->data);
+    if (frompart < 0) {
+       fprintf(STDERR, "vos: could not interpret partition name '%s'\n",
+               as->parms[2].items->data);
+       return EINVAL;
+    }
+    if (!IsPartValid(frompart, fromserver, &code)) {   /*check for validity of the partition */
+       if (code)
+           PrintError("", code);
+       else
+           fprintf(STDERR,
+                   "vos : partition %s does not exist on the server\n",
+                   as->parms[2].items->data);
+       return ENOENT;
+    }
+
+    topart = volutil_GetPartitionID(as->parms[4].items->data);
+    if (topart < 0) {
+       fprintf(STDERR, "vos: could not interpret partition name '%s'\n",
+               as->parms[4].items->data);
+       return EINVAL;
+    }
+    if (!IsPartValid(topart, toserver, &code)) {       /*check for validity of the partition */
+       if (code)
+           PrintError("", code);
+       else
+           fprintf(STDERR,
+                   "vos : partition %s does not exist on the server\n",
+                   as->parms[4].items->data);
+       return ENOENT;
+    }
+
+    if (as->parms[5].items) {
+       tovolume = as->parms[5].items->data;
+       if (!ISNAMEVALID(tovolume)) {
+           fprintf(STDERR,
+               "vos: the name of the root volume %s exceeds the size limit of %d\n",
+               tovolume, VOLSER_OLDMAXVOLNAME - 10);
+           return E2BIG;
+       }
+       if (!VolNameOK(tovolume)) {
+           fprintf(STDERR,
+               "Illegal volume name %s, should not end in .readonly or .backup\n",
+               tovolume);
+           return EINVAL;
+       }
+       if (IsNumeric(tovolume)) {
+           fprintf(STDERR,
+               "Illegal volume name %s, should not be a number\n",
+               tovolume);
+           return EINVAL;
+       }
+    } else {
+       /* use actual name of source volume */
+       code = UV_ListOneVolume(fromserver, frompart, volid, &p);
+       if (code) {
+           fprintf(STDERR, "vos:cannot access volume %lu\n",
+               (unsigned long)volid);
+           exit(1);
+       }
+       strcpy(toVolName, p->name);
+       tovolume = toVolName;
+       /* save p for size checks later */
+    }
+
+    if (as->parms[6].items) {
+       tovolid = vsu_GetVolumeID(as->parms[6].items->data, cstruct, &err);
+       if (tovolid == 0) {
+           if (err)
+               PrintError("", err);
+           else
+               fprintf(STDERR, "vos: can't find volume ID or name '%s'\n",
+                       as->parms[6].items->data);
+           if (p)
+               free(p);
+           return ENOENT;
+       }
+    } else {
+       tovolid = vsu_GetVolumeID(tovolume, cstruct, &err);
+       if (tovolid == 0) {
+           if (err)
+               PrintError("", err);
+           else
+               fprintf(STDERR, "vos: can't find volume ID or name '%s'\n",
+                       tovolume);
+           if (p)
+               free(p);
+           return ENOENT;
+       }
+    }
+
+    flags = RV_NOVLDB;
+    if (as->parms[7].items) flags |= RV_OFFLINE;
+    if (as->parms[8].items) flags |= RV_RDONLY;
+    if (as->parms[9].items) flags |= RV_NOCLONE;
+    if (as->parms[10].items) flags |= RV_CPINCR;
+
+    MapPartIdIntoName(topart, toPartName);
+    MapPartIdIntoName(frompart, fromPartName);
+
+    /*
+     * check target partition for space to move volume
+     */
+
+    code = UV_PartitionInfo(toserver, toPartName, &partition);
+    if (code) {
+       fprintf(STDERR, "vos: cannot access partition %s\n", toPartName);
+       exit(1);
+    }
+    if (TESTM)
+       fprintf(STDOUT, "target partition %s free space %d\n", toPartName,
+               partition.free);
+
+    /* Don't do this again if we did it above */
+    if (!p) {
+       code = UV_ListOneVolume(fromserver, frompart, volid, &p);
+       if (code) {
+           fprintf(STDERR, "vos:cannot access volume %lu\n",
+               (unsigned long)volid);
+           exit(1);
+       }
+    }
+
+    /* OK if this fails */
+    code = UV_ListOneVolume(toserver, topart, tovolid, &q);
+
+    /* Treat existing volume size as "free" */
+    if (q)
+       p->size = (q->size < p->size) ? p->size - q->size : 0;
+
+    if (partition.free <= p->size) {
+       fprintf(STDERR,
+               "vos: no space on target partition %s to copy volume %lu\n",
+               toPartName, (unsigned long)volid);
+       free(p);
+       if (q) free(q);
+       exit(1);
+    }
+    free(p);
+    if (q) free(q);
+
+    /* successful copy still not guaranteed but shoot for it */
+
+    code =
+       UV_CopyVolume2(volid, fromserver, frompart, tovolume, toserver,
+                      topart, tovolid, flags);
+    if (code) {
+       PrintDiagnostics("shadow", code);
+       return code;
+    }
+    MapPartIdIntoName(topart, toPartName);
+    MapPartIdIntoName(frompart, fromPartName);
+    fprintf(STDOUT, "Volume %lu shadowed from %s %s to %s %s \n",
+           (unsigned long)volid, as->parms[1].items->data, fromPartName,
+           as->parms[4].items->data, toPartName);
+
+    return 0;
+}
+
+
+static
+CloneVolume(as)
+     register struct cmd_syndesc *as;
+{
+    afs_int32 server, part, volid, cloneid, voltype;
+    char partName[10], *volname;
+    afs_int32 code, err, flags;
+    struct nvldbentry entry;
+
+    volid = vsu_GetVolumeID(as->parms[0].items->data, cstruct, &err);
+    if (volid == 0) {
+       if (err)
+           PrintError("", err);
+       else
+           fprintf(STDERR, "vos: can't find volume ID or name '%s'\n",
+                   as->parms[0].items->data);
+       return ENOENT;
+    }
+
+    if (as->parms[1].items || as->parms[2].items) {
+       if (!as->parms[1].items || !as->parms[2].items) {
+           fprintf(STDERR,
+                   "Must specify both -server and -partition options\n");
+           return -1;
+       }
+       server = GetServer(as->parms[1].items->data);
+       if (server == 0) {
+           fprintf(STDERR, "vos: server '%s' not found in host table\n",
+                   as->parms[1].items->data);
+           return ENOENT;
+       }
+       part = volutil_GetPartitionID(as->parms[2].items->data);
+       if (part < 0) {
+           fprintf(STDERR, "vos: could not interpret partition name '%s'\n",
+                   as->parms[2].items->data);
+           return EINVAL;
+       }
+       if (!IsPartValid(part, server, &code)) {        /*check for validity of the partition */
+           if (code)
+               PrintError("", code);
+           else
+               fprintf(STDERR,
+                   "vos : partition %s does not exist on the server\n",
+                   as->parms[2].items->data);
+           return ENOENT;
+        }
+    } else {
+       code = GetVolumeInfo(volid, &server, &part, &voltype, &entry);
+       if (code)
+           return code;
+    }
+
+    volname = 0;
+    if (as->parms[3].items) {
+       volname = as->parms[3].items->data;
+       if (strlen(volname) > VOLSER_OLDMAXVOLNAME - 1) {
+           fprintf(STDERR,
+               "vos: the name of the root volume %s exceeds the size limit of %d\n",
+               volname, VOLSER_OLDMAXVOLNAME - 1);
+           return E2BIG;
+       }
+       if (!VolNameOK(volname)) {
+           fprintf(STDERR,
+               "Illegal volume name %s, should not end in .readonly or .backup\n",
+               volname);
+           return EINVAL;
+       }
+       if (IsNumeric(volname)) {
+           fprintf(STDERR,
+               "Illegal volume name %s, should not be a number\n",
+               volname);
+           return EINVAL;
+       }
+    }
+
+    cloneid = 0;
+    if (as->parms[4].items) {
+       cloneid = vsu_GetVolumeID(as->parms[4].items->data, cstruct, &err);
+       if (cloneid == 0) {
+           if (err)
+               PrintError("", err);
+           else
+               fprintf(STDERR, "vos: can't find volume ID or name '%s'\n",
+                       as->parms[4].items->data);
+           return ENOENT;
+       }
+    }
+
+    flags = 0;
+    if (as->parms[5].items) flags |= RV_OFFLINE;
+    if (as->parms[6].items) flags |= RV_RDONLY;
+
+
+    code = 
+       UV_CloneVolume(server, part, volid, cloneid, volname, flags);
+
+    if (code) {
+       PrintDiagnostics("clone", code);
+       return code;
+    }
+    MapPartIdIntoName(part, partName);
+    fprintf(STDOUT, "Created clone for volume %lu\n",
+           as->parms[0].items->data);
+
+    return 0;
+}
+
+
+static
 BackupVolume(as)
      register struct cmd_syndesc *as;
 {
@@ -4962,6 +5273,8 @@ main(argc, argv)
                "machine name on destination");
     cmd_AddParm(ts, "-topartition", CMD_SINGLE, 0,
                "partition name on destination");
+    cmd_AddParm(ts, "-live", CMD_FLAG, CMD_OPTIONAL,
+               "copy live volume without cloning");
     COMMONPARMS;
 
     ts = cmd_CreateSyntax("copy", CopyVolume, 0, "copy a volume");
@@ -4974,6 +5287,36 @@ main(argc, argv)
                "machine name on destination");
     cmd_AddParm(ts, "-topartition", CMD_SINGLE, 0,
                "partition name on destination");
+    cmd_AddParm(ts, "-offline", CMD_FLAG, CMD_OPTIONAL,
+               "leave new volume offline");
+    cmd_AddParm(ts, "-readonly", CMD_FLAG, CMD_OPTIONAL,
+               "make new volume read-only");
+    cmd_AddParm(ts, "-live", CMD_FLAG, CMD_OPTIONAL,
+               "copy live volume without cloning");
+    COMMONPARMS;
+
+    ts = cmd_CreateSyntax("shadow", ShadowVolume, 0,
+                         "make or update a shadow volume");
+    cmd_AddParm(ts, "-id", CMD_SINGLE, 0, "volume name or ID on source");
+    cmd_AddParm(ts, "-fromserver", CMD_SINGLE, 0, "machine name on source");
+    cmd_AddParm(ts, "-frompartition", CMD_SINGLE, 0,
+               "partition name on source");
+    cmd_AddParm(ts, "-toserver", CMD_SINGLE, 0,
+               "machine name on destination");
+    cmd_AddParm(ts, "-topartition", CMD_SINGLE, 0,
+               "partition name on destination");
+    cmd_AddParm(ts, "-toname", CMD_SINGLE, CMD_OPTIONAL,
+               "volume name on destination");
+    cmd_AddParm(ts, "-toid", CMD_SINGLE, CMD_OPTIONAL,
+               "volume ID on destination");
+    cmd_AddParm(ts, "-offline", CMD_FLAG, CMD_OPTIONAL,
+               "leave shadow volume offline");
+    cmd_AddParm(ts, "-readonly", CMD_FLAG, CMD_OPTIONAL,
+               "make shadow volume read-only");
+    cmd_AddParm(ts, "-live", CMD_FLAG, CMD_OPTIONAL,
+               "copy live volume without cloning");
+    cmd_AddParm(ts, "-incremental", CMD_FLAG, CMD_OPTIONAL,
+               "do incremental update if target exists");
     COMMONPARMS;
 
     ts = cmd_CreateSyntax("backup", BackupVolume, 0,
@@ -4981,6 +5324,21 @@ main(argc, argv)
     cmd_AddParm(ts, "-id", CMD_SINGLE, 0, "volume name or ID");
     COMMONPARMS;
 
+    ts = cmd_CreateSyntax("clone", CloneVolume, 0,
+                         "make clone of a volume");
+    cmd_AddParm(ts, "-id", CMD_SINGLE, 0, "volume name or ID");
+    cmd_AddParm(ts, "-server", CMD_SINGLE, CMD_OPTIONAL, "server");
+    cmd_AddParm(ts, "-partition", CMD_SINGLE, CMD_OPTIONAL, "partition");
+    cmd_AddParm(ts, "-toname", CMD_SINGLE, CMD_OPTIONAL,
+               "volume name on destination");
+    cmd_AddParm(ts, "-toid", CMD_SINGLE, CMD_OPTIONAL,
+               "volume ID on destination");
+    cmd_AddParm(ts, "-offline", CMD_FLAG, CMD_OPTIONAL,
+               "leave clone volume offline");
+    cmd_AddParm(ts, "-readonly", CMD_FLAG, CMD_OPTIONAL,
+               "make clone volume read-only, not readwrite");
+    COMMONPARMS;
+
     ts = cmd_CreateSyntax("release", ReleaseVolume, 0, "release a volume");
     cmd_AddParm(ts, "-id", CMD_SINGLE, 0, "volume name or ID");
     cmd_AddParm(ts, "-force", CMD_FLAG, CMD_OPTIONAL,
index fe03d06..6ca2273 100644 (file)
@@ -1014,12 +1014,15 @@ sigint_handler(int x)
 }
 
 /* Move volume <afromvol> on <afromserver> <afrompart> to <atoserver>
- * <atopart>. The operation is almost idempotent 
+ * <atopart>.  The operation is almost idempotent.  The following
+ * flags are recognized:
+ * 
+ *     RV_NOCLONE - don't use a copy clone
  */
 
 int
-UV_MoveVolume(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
-             afs_int32 atoserver, afs_int32 atopart)
+UV_MoveVolume2(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
+              afs_int32 atoserver, afs_int32 atopart, int flags)
 {
     struct rx_connection *toconn, *fromconn;
     afs_int32 fromtid, totid, clonetid;
@@ -1243,26 +1246,32 @@ UV_MoveVolume(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
           afromvol);
     VDONE;
 
-    /* Get a clone id */
-    VPRINT1("Allocating new volume id for clone of volume %u ...", afromvol);
-    newVol = 0;
-    vcode = ubik_Call(VL_GetNewVolumeId, cstruct, 0, 1, &newVol);
-    EGOTO1(mfail, vcode,
-          "Could not get an ID for the clone of volume %u from the VLDB\n",
-          afromvol);
-    VDONE;
+    if (!(flags & RV_NOCLONE)) {
+       /* Get a clone id */
+       VPRINT1("Allocating new volume id for clone of volume %u ...",
+               afromvol);
+       newVol = 0;
+       vcode = ubik_Call(VL_GetNewVolumeId, cstruct, 0, 1, &newVol);
+       EGOTO1(mfail, vcode,
+              "Could not get an ID for the clone of volume %u from the VLDB\n",
+              afromvol);
+       VDONE;
 
-    /* Do the clone. Default flags on clone are set to delete on salvage and out of service */
-    VPRINT1("Cloning source volume %u ...", afromvol);
-    strcpy(vname, "move-clone-temp");
-    code = AFSVolClone(fromconn, fromtid, 0, readonlyVolume, vname, &newVol);
-    EGOTO1(mfail, code, "Failed to clone the source volume %u\n", afromvol);
-    VDONE;
+       /* Do the clone. Default flags on clone are set to delete on salvage and out of service */
+       VPRINT1("Cloning source volume %u ...", afromvol);
+       strcpy(vname, "move-clone-temp");
+       code =
+           AFSVolClone(fromconn, fromtid, 0, readonlyVolume, vname, &newVol);
+       EGOTO1(mfail, code, "Failed to clone the source volume %u\n",
+              afromvol);
+       VDONE;
+    }
 
     /* lookup the name of the volume we just cloned */
     volid = afromvol;
     code = AFSVolGetName(fromconn, fromtid, &volName);
-    EGOTO1(mfail, code, "Failed to get the name of the volume %u\n", newVol);
+    EGOTO1(mfail, code, "Failed to get the name of the volume %u\n",
+          afromvol);
 
     VPRINT1("Ending the transaction on the source volume %u ...", afromvol);
     rcode = 0;
@@ -1279,27 +1288,39 @@ UV_MoveVolume(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
      * Create the destination volume
      * ***/
 
-    VPRINT1("Starting transaction on the cloned volume %u ...", newVol);
-    code =
-       AFSVolTransCreate(fromconn, newVol, afrompart, ITOffline, &clonetid);
-    EGOTO1(mfail, code,
-          "Failed to start a transaction on the cloned volume%u\n", newVol);
-    VDONE;
+    if (!(flags & RV_NOCLONE)) {
+       /* All of this is to get the fromDate */
+       VPRINT1("Starting transaction on the cloned volume %u ...", newVol);
+       code =
+           AFSVolTransCreate(fromconn, newVol, afrompart, ITOffline,
+                             &clonetid);
+       EGOTO1(mfail, code,
+              "Failed to start a transaction on the cloned volume%u\n",
+              newVol);
+       VDONE;
 
-    VPRINT1("Setting flags on cloned volume %u ...", newVol);
-    code = AFSVolSetFlags(fromconn, clonetid, VTDeleteOnSalvage | VTOutOfService);     /*redundant */
-    EGOTO1(mfail, code, "Could not set falgs on the cloned volume %u\n",
-          newVol);
-    VDONE;
+       VPRINT1("Setting flags on cloned volume %u ...", newVol);
+       code =
+           AFSVolSetFlags(fromconn, clonetid,
+                          VTDeleteOnSalvage | VTOutOfService); /*redundant */
+       EGOTO1(mfail, code, "Could not set flags on the cloned volume %u\n",
+              newVol);
+       VDONE;
 
-    /* remember time from which we've dumped the volume */
-    VPRINT1("Getting status of cloned volume %u ...", newVol);
-    code = AFSVolGetStatus(fromconn, clonetid, &tstatus);
-    EGOTO1(mfail, code, "Failed to get the status of the cloned volume %u\n",
-          newVol);
-    VDONE;
+       /* remember time from which we've dumped the volume */
+       VPRINT1("Getting status of cloned volume %u ...", newVol);
+       code = AFSVolGetStatus(fromconn, clonetid, &tstatus);
+       EGOTO1(mfail, code,
+              "Failed to get the status of the cloned volume %u\n",
+              newVol);
+       VDONE;
+
+       fromDate = tstatus.creationDate - CLOCKSKEW;
+    } else {
+       /* With RV_NOCLONE, just do a full copy from the source */
+       fromDate = 0;
+    }
 
-    fromDate = tstatus.creationDate - CLOCKSKEW;
 
 #ifdef ENABLE_BUGFIX_1165
     /*
@@ -1374,25 +1395,31 @@ UV_MoveVolume(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
     destination.destPort = AFSCONF_VOLUMEPORT;
     destination.destSSID = 1;
 
-    /* Copy the clone to the new volume */
-    VPRINT2("Dumping from clone %u on source to volume %u on destination ...",
-           newVol, afromvol);
     strncpy(cookie.name, tmpName, VOLSER_OLDMAXVOLNAME);
     cookie.type = RWVOL;
     cookie.parent = entry.volumeId[RWVOL];
     cookie.clone = 0;
-    code = AFSVolForward(fromconn, clonetid, 0, &destination, totid, &cookie);
-    EGOTO1(mfail, code, "Failed to move data for the volume %u\n", volid);
-    VDONE;
 
-    VPRINT1("Ending transaction on cloned volume %u ...", newVol);
-    code = AFSVolEndTrans(fromconn, clonetid, &rcode);
-    if (!code)
-       code = rcode;
-    clonetid = 0;
-    EGOTO1(mfail, code,
-          "Failed to end the transaction on the cloned volume %u\n", newVol);
-    VDONE;
+    if (!(flags & RV_NOCLONE)) {
+       /* Copy the clone to the new volume */
+       VPRINT2("Dumping from clone %u on source to volume %u on destination ...",
+               newVol, afromvol);
+       code =
+           AFSVolForward(fromconn, clonetid, 0, &destination, totid,
+                         &cookie);
+       EGOTO1(mfail, code, "Failed to move data for the volume %u\n", volid);
+       VDONE;
+
+       VPRINT1("Ending transaction on cloned volume %u ...", newVol);
+       code = AFSVolEndTrans(fromconn, clonetid, &rcode);
+       if (!code)
+           code = rcode;
+       clonetid = 0;
+       EGOTO1(mfail, code,
+              "Failed to end the transaction on the cloned volume %u\n",
+              newVol);
+       VDONE;
+    }
 
     /* ***
      * reattach to the main-line volume, and incrementally dump it.
@@ -1406,14 +1433,16 @@ UV_MoveVolume(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
     VDONE;
 
     /* now do the incremental */
-    VPRINT1
-       ("Doing the incremental dump from source to destination for volume %u ... ",
+    VPRINT2
+       ("Doing the%s dump from source to destination for volume %u ... ",
+        (flags & RV_NOCLONE) ? "" : " incremental",
         afromvol);
     code =
        AFSVolForward(fromconn, fromtid, fromDate, &destination, totid,
                      &cookie);
-    EGOTO(mfail, code,
-         "Failed to do the incremental dump from rw volume on old site to rw volume on newsite\n");
+    EGOTO1(mfail, code,
+          "Failed to do the%s dump from rw volume on old site to rw volume on newsite\n",
+         (flags & RV_NOCLONE) ? "" : " incremental");
     VDONE;
 
     /* now adjust the flags so that the new volume becomes official */
@@ -1582,27 +1611,33 @@ UV_MoveVolume(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
        code = 0;               /* no backup volume? that's okay */
 
     fromtid = 0;
-    VPRINT1("Starting transaction on the cloned volume %u ...", newVol);
-    code =
-       AFSVolTransCreate(fromconn, newVol, afrompart, ITOffline, &clonetid);
-    EGOTO1(mfail, code,
-          "Failed to start a transaction on the cloned volume%u\n", newVol);
-    VDONE;
+    if (!(flags & RV_NOCLONE)) {
+       VPRINT1("Starting transaction on the cloned volume %u ...", newVol);
+       code =
+           AFSVolTransCreate(fromconn, newVol, afrompart, ITOffline,
+                             &clonetid);
+       EGOTO1(mfail, code,
+              "Failed to start a transaction on the cloned volume%u\n",
+              newVol);
+       VDONE;
 
-    /* now delete the clone */
-    VPRINT1("Deleting the cloned volume %u ...", newVol);
-    code = AFSVolDeleteVolume(fromconn, clonetid);
-    EGOTO1(mfail, code, "Failed to delete the cloned volume %u\n", newVol);
-    VDONE;
+       /* now delete the clone */
+       VPRINT1("Deleting the cloned volume %u ...", newVol);
+       code = AFSVolDeleteVolume(fromconn, clonetid);
+       EGOTO1(mfail, code, "Failed to delete the cloned volume %u\n",
+              newVol);
+       VDONE;
 
-    VPRINT1("Ending transaction on cloned volume %u ...", newVol);
-    code = AFSVolEndTrans(fromconn, clonetid, &rcode);
-    if (!code)
-       code = rcode;
-    clonetid = 0;
-    EGOTO1(mfail, code,
-          "Failed to end the transaction on the cloned volume %u\n", newVol);
-    VDONE;
+       VPRINT1("Ending transaction on cloned volume %u ...", newVol);
+       code = AFSVolEndTrans(fromconn, clonetid, &rcode);
+       if (!code)
+           code = rcode;
+       clonetid = 0;
+       EGOTO1(mfail, code,
+              "Failed to end the transaction on the cloned volume %u\n",
+              newVol);
+       VDONE;
+    }
 
     /* fall through */
     /* END OF MOVE */
@@ -1652,8 +1687,8 @@ UV_MoveVolume(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
        if (code || rcode) {
            VPRINT("\n");
            fprintf(STDERR,
-                   "Could not end transaction on the source's clone volume %lu\n",
-                   (unsigned long)newVol);
+                   "Could not end transaction on the source volume %lu\n",
+                   (unsigned long)afromvol);
            if (!error)
                error = (code ? code : rcode);
        }
@@ -1948,23 +1983,39 @@ UV_MoveVolume(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
 }
 
 
-/* Move volume <afromvol> on <afromserver> <afrompart> to <atoserver>
- * <atopart>. The operation is almost idempotent 
- */
+int
+UV_MoveVolume(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
+             afs_int32 atoserver, afs_int32 atopart)
+{
+    return UV_MoveVolume2(afromvol, afromserver, afrompart,
+                         atoserver, atopart, 0);
+}
 
+
+/* Copy volume <afromvol> from <afromserver> <afrompart> to <atoserver>
+ * <atopart>.  The new volume is named by <atovolname>.  The new volume
+ * has ID <atovolid> if that is nonzero; otherwise a new ID is allocated
+ * from the VLDB.  the following flags are supported:
+ * 
+ *     RV_RDONLY  - target volume is RO
+ *     RV_OFFLINE - leave target volume offline
+ *     RV_CPINCR  - do incremental dump if target exists
+ *     RV_NOVLDB  - don't create/update VLDB entry
+ *     RV_NOCLONE - don't use a copy clone
+ */
 int
-UV_CopyVolume(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
-             char *atovolname, afs_int32 atoserver, afs_int32 atopart)
+UV_CopyVolume2(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
+              char *atovolname, afs_int32 atoserver, afs_int32 atopart,
+              afs_int32 atovolid, int flags)
 {
     struct rx_connection *toconn, *fromconn;
     afs_int32 fromtid, totid, clonetid;
     char vname[64];
-    char tmpName[VOLSER_MAXVOLNAME + 1];
     afs_int32 rcode;
-    afs_int32 fromDate;
+    afs_int32 fromDate, cloneFromDate;
     struct restoreCookie cookie;
     register afs_int32 vcode, code;
-    afs_int32 cloneVol, newVol;
+    afs_int32 cloneVol, newVol, volflag;
     struct volser_status tstatus;
     struct destServer destination;
 
@@ -2009,80 +2060,121 @@ UV_CopyVolume(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
      * clone the read/write volume locally.
      * ***/
 
-    VPRINT1("Starting transaction on source volume %u ...", afromvol);
-    code = AFSVolTransCreate(fromconn, afromvol, afrompart, ITBusy, &fromtid);
-    EGOTO1(mfail, code, "Failed to create transaction on the volume %u\n",
-          afromvol);
-    VDONE;
+    cloneVol = 0;
+    if (!(flags & RV_NOCLONE)) {
+       VPRINT1("Starting transaction on source volume %u ...", afromvol);
+       code = AFSVolTransCreate(fromconn, afromvol, afrompart, ITBusy,
+                                &fromtid);
+       EGOTO1(mfail, code, "Failed to create transaction on the volume %u\n",
+              afromvol);
+       VDONE;
 
-    /* Get a clone id */
-    VPRINT1("Allocating new volume id for clone of volume %u ...", afromvol);
-    newVol = 0;
-    vcode = ubik_Call(VL_GetNewVolumeId, cstruct, 0, 1, &cloneVol);
-    EGOTO1(mfail, vcode,
+       /* Get a clone id */
+       VPRINT1("Allocating new volume id for clone of volume %u ...",
+               afromvol);
+       cloneVol = 0;
+       vcode = ubik_Call(VL_GetNewVolumeId, cstruct, 0, 1, &cloneVol);
+       EGOTO1(mfail, vcode,
           "Could not get an ID for the clone of volume %u from the VLDB\n",
           afromvol);
-    VDONE;
+       VDONE;
+    }
 
-    /* Get a new volume id */
-    VPRINT1("Allocating new volume id for copy of volume %u ...", afromvol);
-    newVol = 0;
-    vcode = ubik_Call(VL_GetNewVolumeId, cstruct, 0, 1, &newVol);
-    EGOTO1(mfail, vcode,
-          "Could not get an ID for the copy of volume %u from the VLDB\n",
-          afromvol);
-    VDONE;
+    if (atovolid) {
+       newVol = atovolid;
+    } else {
+       /* Get a new volume id */
+       VPRINT1("Allocating new volume id for copy of volume %u ...", afromvol);
+       newVol = 0;
+       vcode = ubik_Call(VL_GetNewVolumeId, cstruct, 0, 1, &newVol);
+       EGOTO1(mfail, vcode,
+              "Could not get an ID for the copy of volume %u from the VLDB\n",
+              afromvol);
+       VDONE;
+    }
 
-    /* Do the clone. Default flags on clone are set to delete on salvage and out of service */
-    VPRINT1("Cloning source volume %u ...", afromvol);
-    strcpy(vname, "copy-clone-temp");
-    code =
-       AFSVolClone(fromconn, fromtid, 0, readonlyVolume, vname, &cloneVol);
-    EGOTO1(mfail, code, "Failed to clone the source volume %u\n", afromvol);
-    VDONE;
+    if (!(flags & RV_NOCLONE)) {
+       /* Do the clone. Default flags on clone are set to delete on salvage and out of service */
+       VPRINT1("Cloning source volume %u ...", afromvol);
+       strcpy(vname, "copy-clone-temp");
+       code =
+           AFSVolClone(fromconn, fromtid, 0, readonlyVolume, vname,
+                       &cloneVol);
+       EGOTO1(mfail, code, "Failed to clone the source volume %u\n",
+              afromvol);
+       VDONE;
 
-    VPRINT1("Ending the transaction on the source volume %u ...", afromvol);
-    rcode = 0;
-    code = AFSVolEndTrans(fromconn, fromtid, &rcode);
-    fromtid = 0;
-    if (!code)
-       code = rcode;
-    EGOTO1(mfail, code,
-          "Failed to end the transaction on the source volume %u\n",
-          afromvol);
-    VDONE;
+       VPRINT1("Ending the transaction on the source volume %u ...", afromvol);
+       rcode = 0;
+       code = AFSVolEndTrans(fromconn, fromtid, &rcode);
+       fromtid = 0;
+       if (!code)
+           code = rcode;
+       EGOTO1(mfail, code,
+              "Failed to end the transaction on the source volume %u\n",
+              afromvol);
+       VDONE;
+    }
 
     /* ***
      * Create the destination volume
      * ***/
 
-    VPRINT1("Starting transaction on the cloned volume %u ...", cloneVol);
-    code =
-       AFSVolTransCreate(fromconn, cloneVol, afrompart, ITOffline,
+    if (!(flags & RV_NOCLONE)) {
+       VPRINT1("Starting transaction on the cloned volume %u ...", cloneVol);
+       code =
+           AFSVolTransCreate(fromconn, cloneVol, afrompart, ITOffline,
                          &clonetid);
-    EGOTO1(mfail, code,
-          "Failed to start a transaction on the cloned volume%u\n",
-          cloneVol);
-    VDONE;
+       EGOTO1(mfail, code,
+              "Failed to start a transaction on the cloned volume%u\n",
+              cloneVol);
+       VDONE;
 
-    VPRINT1("Setting flags on cloned volume %u ...", cloneVol);
-    code = AFSVolSetFlags(fromconn, clonetid, VTDeleteOnSalvage | VTOutOfService);     /*redundant */
-    EGOTO1(mfail, code, "Could not set falgs on the cloned volume %u\n",
-          cloneVol);
-    VDONE;
+       VPRINT1("Setting flags on cloned volume %u ...", cloneVol);
+       code =
+           AFSVolSetFlags(fromconn, clonetid,
+                          VTDeleteOnSalvage | VTOutOfService); /*redundant */
+       EGOTO1(mfail, code, "Could not set flags on the cloned volume %u\n",
+              cloneVol);
+       VDONE;
 
-    /* remember time from which we've dumped the volume */
-    VPRINT1("Getting status of cloned volume %u ...", cloneVol);
-    code = AFSVolGetStatus(fromconn, clonetid, &tstatus);
-    EGOTO1(mfail, code, "Failed to get the status of the cloned volume %u\n",
-          cloneVol);
-    VDONE;
+       /* remember time from which we've dumped the volume */
+       VPRINT1("Getting status of cloned volume %u ...", cloneVol);
+       code = AFSVolGetStatus(fromconn, clonetid, &tstatus);
+       EGOTO1(mfail, code,
+              "Failed to get the status of the cloned volume %u\n",
+              cloneVol);
+       VDONE;
 
-    fromDate = tstatus.creationDate - CLOCKSKEW;
+       fromDate = tstatus.creationDate - CLOCKSKEW;
+    } else {
+       fromDate = 0;
+    }
 
     /* create a volume on the target machine */
+    cloneFromDate = 0;
     code = AFSVolTransCreate(toconn, newVol, atopart, ITOffline, &totid);
     if (!code) {
+       if ((flags & RV_CPINCR)) {
+           VPRINT1("Getting status of pre-existing volume %u ...", newVol);
+           code = AFSVolGetStatus(toconn, totid, &tstatus);
+           EGOTO1(mfail, code,
+                  "Failed to get the status of the pre-existing volume %u\n",
+                  newVol);
+           VDONE;
+
+           /* Using the update date should be OK here, but add some fudge */
+           cloneFromDate = tstatus.updateDate - CLOCKSKEW;
+           if ((flags & RV_NOCLONE))
+               fromDate = cloneFromDate;
+
+           /* XXX We should check that the source volume's creationDate is
+            * XXX not newer than the existing target volume, and if not,
+            * XXX throw away the existing target and do a full dump. */
+
+           goto cpincr;
+       }
+
        /* Delete the existing volume.
         * While we are deleting the volume in these steps, the transaction
         * we started against the cloned volume (clonetid above) will be
@@ -2110,14 +2202,13 @@ UV_CopyVolume(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
 
     VPRINT1("Creating the destination volume %u ...", newVol);
     code =
-       AFSVolCreateVolume(toconn, atopart, atovolname, volser_RW, newVol,
-                          &newVol, &totid);
+       AFSVolCreateVolume(toconn, atopart, atovolname,
+                          (flags & RV_RDONLY) ? volser_RO : volser_RW,
+                          newVol, &newVol, &totid);
     EGOTO1(mfail, code, "Failed to create the destination volume %u\n",
           newVol);
     VDONE;
 
-    strncpy(tmpName, atovolname, VOLSER_OLDMAXVOLNAME);
-
     VPRINT1("Setting volume flags on destination volume %u ...", newVol);
     code =
        AFSVolSetFlags(toconn, totid, (VTDeleteOnSalvage | VTOutOfService));
@@ -2125,38 +2216,46 @@ UV_CopyVolume(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
           "Failed to set the flags on the destination volume %u\n", newVol);
     VDONE;
 
-    /***
-     * Now dump the clone to the new volume
-     ***/
+cpincr:
 
     destination.destHost = ntohl(atoserver);
     destination.destPort = AFSCONF_VOLUMEPORT;
     destination.destSSID = 1;
 
+    strncpy(cookie.name, atovolname, VOLSER_OLDMAXVOLNAME);
+    cookie.type = (flags & RV_RDONLY) ? ROVOL : RWVOL;
+    cookie.parent = 0;
+    cookie.clone = 0;
 
-/* probably should have some code here that checks to see if we are copying to same server
-and partition - if so, just use a clone to save disk space */
+    /***
+     * Now dump the clone to the new volume
+     ***/
 
-    /* Copy the clone to the new volume */
-    VPRINT2("Dumping from clone %u on source to volume %u on destination ...",
+    if (!(flags & RV_NOCLONE)) {
+       /* XXX probably should have some code here that checks to see if
+        * XXX we are copying to same server and partition - if so, just
+        * XXX use a clone to save disk space */
+
+       /* Copy the clone to the new volume */
+       VPRINT2("Dumping from clone %u on source to volume %u on destination ...",
            cloneVol, newVol);
-    strncpy(cookie.name, tmpName, VOLSER_OLDMAXVOLNAME);
-    cookie.type = RWVOL;
-    cookie.parent = 0;
-    cookie.clone = 0;
-    code = AFSVolForward(fromconn, clonetid, 0, &destination, totid, &cookie);
-    EGOTO1(mfail, code, "Failed to move data for the volume %u\n", newVol);
-    VDONE;
+       code =
+           AFSVolForward(fromconn, clonetid, cloneFromDate, &destination,
+                         totid, &cookie);
+       EGOTO1(mfail, code, "Failed to move data for the volume %u\n",
+              newVol);
+       VDONE;
 
-    VPRINT1("Ending transaction on cloned volume %u ...", cloneVol);
-    code = AFSVolEndTrans(fromconn, clonetid, &rcode);
-    if (!code)
-       code = rcode;
-    clonetid = 0;
-    EGOTO1(mfail, code,
-          "Failed to end the transaction on the cloned volume %u\n",
-          cloneVol);
-    VDONE;
+       VPRINT1("Ending transaction on cloned volume %u ...", cloneVol);
+       code = AFSVolEndTrans(fromconn, clonetid, &rcode);
+       if (!code)
+           code = rcode;
+       clonetid = 0;
+       EGOTO1(mfail, code,
+              "Failed to end the transaction on the cloned volume %u\n",
+              cloneVol);
+       VDONE;
+    }
 
     /* ***
      * reattach to the main-line volume, and incrementally dump it.
@@ -2170,18 +2269,21 @@ and partition - if so, just use a clone to save disk space */
     VDONE;
 
     /* now do the incremental */
-    VPRINT1
-       ("Doing the incremental dump from source to destination for volume %u ... ",
+    VPRINT2
+       ("Doing the%s dump from source to destination for volume %u ... ",
+        (flags & RV_NOCLONE) ? "" : " incremental",
         afromvol);
     code =
        AFSVolForward(fromconn, fromtid, fromDate, &destination, totid,
                      &cookie);
-    EGOTO(mfail, code,
-         "Failed to do the incremental dump from rw volume on old site to rw volume on newsite\n");
+    EGOTO1(mfail, code,
+          "Failed to do the%s dump from old site to new site\n",
+          afromvol);
     VDONE;
 
     VPRINT1("Setting volume flags on destination volume %u ...", newVol);
-    code = AFSVolSetFlags(toconn, totid, 0);
+    volflag = ((flags & RV_OFFLINE) ? VTOutOfService : 0);     /* off or on-line */
+    code = AFSVolSetFlags(toconn, totid, volflag);
     EGOTO(mfail, code,
          "Failed to set the flags to make destination volume online\n");
     VDONE;
@@ -2208,59 +2310,65 @@ and partition - if so, just use a clone to save disk space */
     VDONE;
 
     fromtid = 0;
-    VPRINT1("Starting transaction on the cloned volume %u ...", cloneVol);
-    code =
-       AFSVolTransCreate(fromconn, cloneVol, afrompart, ITOffline,
-                         &clonetid);
-    EGOTO1(mfail, code,
-          "Failed to start a transaction on the cloned volume%u\n",
-          cloneVol);
-    VDONE;
 
-    /* now delete the clone */
-    VPRINT1("Deleting the cloned volume %u ...", cloneVol);
-    code = AFSVolDeleteVolume(fromconn, clonetid);
-    EGOTO1(mfail, code, "Failed to delete the cloned volume %u\n", cloneVol);
-    VDONE;
+    if (!(flags & RV_NOCLONE)) {
+       VPRINT1("Starting transaction on the cloned volume %u ...", cloneVol);
+       code =
+           AFSVolTransCreate(fromconn, cloneVol, afrompart, ITOffline,
+                             &clonetid);
+       EGOTO1(mfail, code,
+              "Failed to start a transaction on the cloned volume%u\n",
+              cloneVol);
+       VDONE;
 
-    VPRINT1("Ending transaction on cloned volume %u ...", cloneVol);
-    code = AFSVolEndTrans(fromconn, clonetid, &rcode);
-    if (!code)
-       code = rcode;
-    clonetid = 0;
-    EGOTO1(mfail, code,
-          "Failed to end the transaction on the cloned volume %u\n",
-          cloneVol);
-    VDONE;
+       /* now delete the clone */
+       VPRINT1("Deleting the cloned volume %u ...", cloneVol);
+       code = AFSVolDeleteVolume(fromconn, clonetid);
+       EGOTO1(mfail, code, "Failed to delete the cloned volume %u\n",
+              cloneVol);
+       VDONE;
 
-    /* create the vldb entry for the copied volume */
-    strncpy(newentry.name, atovolname, VOLSER_OLDMAXVOLNAME);
-    newentry.nServers = 1;
-    newentry.serverNumber[0] = atoserver;
-    newentry.serverPartition[0] = atopart;
-    newentry.flags = RW_EXISTS;        /* this records that rw volume exists */
-    newentry.serverFlags[0] = ITSRWVOL;        /*this rep site has rw vol */
-    newentry.volumeId[RWVOL] = newVol;
-    newentry.volumeId[ROVOL] = 0;
-    newentry.volumeId[BACKVOL] = 0;
-    newentry.cloneId = 0;
-    /*map into right byte order, before passing to xdr, the stuff has to be in host
-     * byte order. Xdr converts it into network order */
-    MapNetworkToHost(&newentry, &storeEntry);
-    /* create the vldb entry */
-    vcode = VLDB_CreateEntry(&storeEntry);
-    if (vcode) {
-       fprintf(STDERR,
-               "Could not create a VLDB entry for the volume %s %lu\n",
-               atovolname, (unsigned long)newVol);
-       /*destroy the created volume */
-       VPRINT1("Deleting the newly created volume %u\n", newVol);
-       AFSVolDeleteVolume(toconn, totid);
-       error = vcode;
-       goto mfail;
+       VPRINT1("Ending transaction on cloned volume %u ...", cloneVol);
+       code = AFSVolEndTrans(fromconn, clonetid, &rcode);
+       if (!code)
+           code = rcode;
+       clonetid = 0;
+       EGOTO1(mfail, code,
+              "Failed to end the transaction on the cloned volume %u\n",
+              cloneVol);
+       VDONE;
+    }
+
+    if (!(flags & RV_NOVLDB)) {
+       /* create the vldb entry for the copied volume */
+       strncpy(newentry.name, atovolname, VOLSER_OLDMAXVOLNAME);
+       newentry.nServers = 1;
+       newentry.serverNumber[0] = atoserver;
+       newentry.serverPartition[0] = atopart;
+       newentry.flags = (flags & RV_RDONLY) ? RO_EXISTS : RW_EXISTS;
+       newentry.serverFlags[0] = (flags & RV_RDONLY) ? ITSROVOL : ITSRWVOL;
+       newentry.volumeId[RWVOL] = newVol;
+       newentry.volumeId[ROVOL] = (flags & RV_RDONLY) ? newVol : 0;
+       newentry.volumeId[BACKVOL] = 0;
+       newentry.cloneId = 0;
+       /*map into right byte order, before passing to xdr, the stuff has to be in host
+        * byte order. Xdr converts it into network order */
+       MapNetworkToHost(&newentry, &storeEntry);
+       /* create the vldb entry */
+       vcode = VLDB_CreateEntry(&storeEntry);
+       if (vcode) {
+           fprintf(STDERR,
+                   "Could not create a VLDB entry for the volume %s %lu\n",
+                   atovolname, (unsigned long)newVol);
+           /*destroy the created volume */
+           VPRINT1("Deleting the newly created volume %u\n", newVol);
+           AFSVolDeleteVolume(toconn, totid);
+           error = vcode;
+           goto mfail;
+       }
+       VPRINT2("Created the VLDB entry for the volume %s %u\n", atovolname,
+               newVol);
     }
-    VPRINT2("Created the VLDB entry for the volume %s %u\n", atovolname,
-           newVol);
 
     /* normal cleanup code */
 
@@ -2389,6 +2497,13 @@ and partition - if so, just use a clone to save disk space */
 }
 
 
+int
+UV_CopyVolume(afs_int32 afromvol, afs_int32 afromserver, afs_int32 afrompart,
+             char *atovolname, afs_int32 atoserver, afs_int32 atopart)
+{
+    return UV_CopyVolume2(afromvol, afromserver, afrompart,
+                          atovolname, atoserver, atopart, 0, 0);
+}
 
 
 
@@ -2647,6 +2762,196 @@ UV_BackupVolume(afs_int32 aserver, afs_int32 apart, afs_int32 avolid)
     return error;
 }
 
+/* Make a new clone of volume <avolid> on <aserver> and <apart> 
+ * using volume ID <acloneid>, or a new ID allocated from the VLDB.
+ * The new volume is named by <aname>, or by appending ".clone" to
+ * the existing name if <aname> is NULL.  The following flags are
+ * supported:
+ * 
+ *     RV_RDONLY  - target volume is RO
+ *     RV_OFFLINE - leave target volume offline
+ */
+
+int
+UV_CloneVolume(afs_int32 aserver, afs_int32 apart, afs_int32 avolid,
+              afs_int32 acloneid, char *aname, int flags)
+{
+    struct rx_connection *aconn = (struct rx_connection *)0;
+    afs_int32 ttid = 0, btid = 0;
+    afs_int32 code = 0, rcode = 0;
+    char vname[VOLSER_MAXVOLNAME + 1];
+    afs_int32 error = 0;
+    int backexists = 1;
+    volEntries volumeInfo;
+
+    aconn = UV_Bind(aserver, AFSCONF_VOLUMEPORT);
+
+    if (!aname) {
+       volumeInfo.volEntries_val = (volintInfo *) 0;
+       volumeInfo.volEntries_len = 0;
+       code = AFSVolListOneVolume(aconn, apart, avolid, &volumeInfo);
+       if (code) {
+           fprintf(stderr, "Could not get info for volume %lu\n",
+                   (unsigned long)avolid);
+           error = code;
+           goto bfail;
+       }
+       strncpy(vname, volumeInfo.volEntries_val[0].name,
+               VOLSER_OLDMAXVOLNAME - 7);
+       vname[VOLSER_OLDMAXVOLNAME - 7] = 0;
+       strcat(vname, ".clone");
+       aname = vname;
+       if (volumeInfo.volEntries_val)
+           free(volumeInfo.volEntries_val);
+    }
+
+    if (!acloneid) {
+       /* Get a clone id */
+       VPRINT1("Allocating new volume id for clone of volume %u ...",
+               avolid);
+       code = ubik_Call(VL_GetNewVolumeId, cstruct, 0, 1, &acloneid);
+       EGOTO1(bfail, code,
+          "Could not get an ID for the clone of volume %u from the VLDB\n",
+          avolid);
+       VDONE;
+    }
+
+    /* Test to see if the clone volume exists by trying to create
+     * a transaction on the clone volume. We've assumed the clone exists.
+     */
+    /* XXX I wonder what happens if the clone has some other parent... */
+    code = AFSVolTransCreate(aconn, acloneid, apart, ITOffline, &btid);
+    if (code) {
+       if (code != VNOVOL) {
+           fprintf(STDERR, "Could not reach the clone volume %lu\n",
+                   (unsigned long)acloneid);
+           error = code;
+           goto bfail;
+       }
+       backexists = 0;         /* backup volume does not exist */
+    }
+    if (btid) {
+       code = AFSVolEndTrans(aconn, btid, &rcode);
+       btid = 0;
+       if (code || rcode) {
+           fprintf(STDERR,
+                   "Could not end transaction on the previous clone volume %lu\n",
+                   (unsigned long)acloneid);
+           error = (code ? code : rcode);
+           goto bfail;
+       }
+    }
+
+    /* Now go ahead and try to clone the RW volume.
+     * First start a transaction on the RW volume 
+     */
+    code = AFSVolTransCreate(aconn, avolid, apart, ITBusy, &ttid);
+    if (code) {
+       fprintf(STDERR, "Could not start a transaction on the volume %lu\n",
+               (unsigned long)avolid);
+       error = code;
+       goto bfail;
+    }
+
+    /* Clone or reclone the volume, depending on whether the backup 
+     * volume exists or not
+     */
+    if (backexists) {
+       VPRINT1("Re-cloning clone volume %u ...", acloneid);
+
+       code = AFSVolReClone(aconn, ttid, acloneid);
+       if (code) {
+           fprintf(STDERR, "Could not re-clone backup volume %lu\n",
+                   (unsigned long)acloneid);
+           error = code;
+           goto bfail;
+       }
+    } else {
+       VPRINT1("Creating a new clone %u ...", acloneid);
+
+       code = AFSVolClone(aconn, ttid, 0,
+                          (flags & RV_RDONLY) ? readonlyVolume : backupVolume,
+                          aname, &acloneid);
+       if (code) {
+           fprintf(STDERR, "Failed to clone the volume %lu\n",
+                   (unsigned long)avolid);
+           error = code;
+           goto bfail;
+       }
+    }
+
+    /* End the transaction on the RW volume */
+    code = AFSVolEndTrans(aconn, ttid, &rcode);
+    ttid = 0;
+    if (code || rcode) {
+       fprintf(STDERR,
+               "Failed to end the transaction on the rw volume %lu\n",
+               (unsigned long)avolid);
+       error = (code ? code : rcode);
+       goto bfail;
+    }
+
+    /* Now go back to the backup volume and bring it on line */
+    if (!(flags & RV_OFFLINE)) {
+       code = AFSVolTransCreate(aconn, acloneid, apart, ITOffline, &btid);
+       if (code) {
+           fprintf(STDERR,
+                   "Failed to start a transaction on the clone volume %lu\n",
+                   (unsigned long)acloneid);
+           error = code;
+           goto bfail;
+       }
+
+       code = AFSVolSetFlags(aconn, btid, 0);
+       if (code) {
+           fprintf(STDERR, "Could not mark the clone volume %lu on line \n",
+                   (unsigned long)acloneid);
+           error = code;
+           goto bfail;
+       }
+
+       code = AFSVolEndTrans(aconn, btid, &rcode);
+       btid = 0;
+       if (code || rcode) {
+           fprintf(STDERR,
+                   "Failed to end the transaction on the clone volume %lu\n",
+                   (unsigned long)acloneid);
+           error = (code ? code : rcode);
+           goto bfail;
+       }
+    }
+
+    VDONE;
+
+  bfail:
+    if (ttid) {
+       code = AFSVolEndTrans(aconn, ttid, &rcode);
+       if (code || rcode) {
+           fprintf(STDERR, "Could not end transaction on the volume %lu\n",
+                   (unsigned long)avolid);
+           if (!error)
+               error = (code ? code : rcode);
+       }
+    }
+
+    if (btid) {
+       code = AFSVolEndTrans(aconn, btid, &rcode);
+       if (code || rcode) {
+           fprintf(STDERR,
+                   "Could not end transaction on the clone volume %lu\n",
+                   (unsigned long)acloneid);
+           if (!error)
+               error = (code ? code : rcode);
+       }
+    }
+
+    if (aconn)
+       rx_DestroyConnection(aconn);
+
+    PrintError("", error);
+    return error;
+}
+
 static int
 DelVol(struct rx_connection *conn, afs_int32 vid, afs_int32 part,
        afs_int32 flags)