2 * Copyright 2000, International Business Machines Corporation and others.
5 * This software has been released under the terms of the IBM Public
6 * License. For details, see the LICENSE file in the top-level source
7 * directory or online at http://www.openafs.org/dl/license10.html
10 #include <afsconfig.h>
11 #include <afs/param.h>
13 #ifdef IGNORE_SOME_GCC_WARNINGS
14 # pragma GCC diagnostic warning "-Wstrict-prototypes"
17 #include <sys/types.h>
23 #include <netinet/in.h>
24 #include <sys/socket.h>
34 #include <afs/tcdata.h>
35 #include <afs/bubasics.h>
36 #include <afs/budb_client.h>
37 #include <afs/butm_prototypes.h>
38 #include <afs/vldbint.h>
39 #include <afs/ktime.h>
40 #include <afs/vlserver.h>
41 #include <afs/volser.h>
42 #include <afs/volint.h>
43 #include <afs/cellconfig.h>
44 #include <afs/bucoord_prototypes.h>
46 #include "butc_internal.h"
47 #include "error_macros.h"
48 #include "butc_xbsa.h"
52 /* GLOBAL CONFIGURATION PARAMETERS */
53 extern int dump_namecheck;
54 extern int queryoperator;
56 extern int forcemultiple;
58 extern struct ubik_client *cstruct;
59 dlqlinkT savedEntries;
60 dlqlinkT entries_to_flush;
62 extern struct rx_connection *UV_Bind();
64 extern afs_int32 groupId;
65 extern afs_int32 BufferSize;
66 extern afs_int32 statusSize;
67 extern FILE *centralLogIO;
68 afs_int32 lastPass = 0;
70 extern afs_int32 xbsaType;
71 char *butcdumpIdStr = "/backup_afs_volume_dumps";
72 extern struct butx_transactionInfo butxInfo;
73 extern char *xbsaObjectOwner;
74 extern char *appObjectOwner;
75 extern char *xbsaSecToken;
76 extern char *xbsalGName;
77 extern char *globalButcLog;
80 afs_int32 dataSize; /* Size of data to read on each rx_Read() call */
81 afs_int32 tapeblocks; /* Number of 16K tape datablocks in buffer (!CONF_XBSA) */
85 * Done 1) dump id generation
86 * Done xx) volume fragment number accounting !! I think.
87 * 2) check abort - check after subroutine calls
88 * Done 3) trailer anomaly
89 * 4) trailer damage indicator after partial dumps ( affects scandump )
90 * Done 5) Ensure mount failure logged
91 * 6) Ensure bucoord status calls work
95 * keep token timeout. If no user reponse (idle time > some period)
96 * and tokens about to time out, terminate dump. This provides at
97 * least something usable.
100 #define DUMPNAME(dumpname, name, dbDumpId) \
102 sprintf(dumpname, "%s", name); \
104 sprintf(dumpname, "%s (DumpId %u)", name, dbDumpId);
106 #if defined(AFS_NT40_ENV) || (defined(AFS_DARWIN_ENV) && !defined(AFS_DARWIN60_ENV)) || defined(AFS_SUN4_ENV)
108 localtime_r(time_t * t, struct tm *tm)
110 memcpy(tm, localtime(t), sizeof(struct tm));
118 int curVolume; /* index in dumpNode of volume */
119 int curVolumeStatus; /* more explicit dump state */
120 afs_uint32 curVolStartPos; /* Starting position of the current volume */
121 afs_uint32 databaseDumpId; /* real dump id, for db */
122 afs_uint32 initialDumpId; /* the initial dump, for appended dumps */
123 afs_int32 volumesDumped; /* # volumes successfully dumped */
124 afs_int32 volumesFailed; /* # volumes that failed to dump */
125 afs_int32 volumesNotDumped; /* # volumes that were not dumped (didn't fail) */
127 /* tape management */
128 char tapeName[TC_MAXTAPENAMELEN];
129 struct butm_tapeInfo *tapeInfoPtr;
130 struct butm_tapeLabel tapeLabel;
131 int wroteLabel; /* If the tape label is written */
133 /* database information */
134 struct budb_dumpEntry lastDump; /* the last dump of this volset */
135 struct budb_dumpEntry dump; /* current dump */
136 struct budb_tapeEntry tape; /* current tape, not used -VA */
138 /* links to existing info */
139 struct dumpNode *node;
142 /* Forward declarations */
144 int makeVolumeHeader(struct volumeHeader *, struct dumpRock *, int);
145 int volumeHeader_hton(struct volumeHeader *, struct volumeHeader *);
146 char retryPrompt(char *, afs_int32, afs_uint32);
147 int getDumpTape(struct dumpRock *, int, afs_int32);
148 int getXBSATape(struct dumpRock *);
149 afs_int32 createDump(struct dumpRock *);
151 /* configuration variables */
152 #define HITEOT(code) ((code == BUTM_IO) || (code == BUTM_EOT) || (code == BUTM_IOCTL))
153 extern int autoQuery;
156 afs_int32 tc_EndMargin;
157 afs_int32 tc_KEndMargin;
158 static char *bufferBlock;
160 /* compute the absolute expiration date */
162 calcExpirationDate(afs_int32 expType, afs_int32 expDate, afs_int32 createTime)
164 struct ktime_date kd;
168 /* expiration date is relative to the creation time of the dump.
169 * This is the only case that requires any work
171 Int32To_ktimeRelDate(expDate, &kd);
172 return (Add_RelDate_to_Time(&kd, createTime));
185 afs_int32 curr_bserver = 0;
186 struct rx_connection *curr_fromconn = (struct rx_connection *)0;
188 struct rx_connection *
189 Bind(afs_int32 server)
192 if (curr_bserver == server) /* Keep connection if have it */
193 return (curr_fromconn);
195 rx_DestroyConnection(curr_fromconn); /* Otherwise get rid of it */
196 curr_fromconn = (struct rx_connection *)0;
201 curr_fromconn = UV_Bind(server, AFSCONF_VOLUMEPORT); /* Establish new connection */
203 curr_bserver = server;
206 return (curr_fromconn);
210 * 1) save the chunksize or otherwise ensure tape space remaining is
211 * check frequently enough
212 * 2) This is called once. For partial dumps, need to
213 * ensure that the tape device is left in the correct state for
217 #define BIGCHUNK 102400
220 dumpVolume(struct tc_dumpDesc * curDump, struct dumpRock * dparamsPtr)
222 struct butm_tapeInfo *tapeInfoPtr = dparamsPtr->tapeInfoPtr;
223 struct dumpNode *nodePtr = dparamsPtr->node;
224 afs_int32 taskId = nodePtr->taskID;
227 afs_int32 volumeFlags;
228 afs_int32 kRemaining;
229 afs_int32 rc, code = 0;
231 afs_uint32 volBytesRead;
232 afs_uint32 chunkSize;
233 afs_int32 bytesread; /* rx reads */
234 int endofvolume = 0; /* Have we read all volume data */
237 struct volumeHeader hostVolumeHeader;
239 struct rx_call *fromcall = (struct rx_call *)0;
240 struct rx_connection *fromconn;
241 afs_int32 updatedate, fromtid = 0;
242 volEntries volumeInfo;
243 afs_int32 bytesWritten;
244 afs_uint32 statuscount = statusSize, tsize = 0;
246 dparamsPtr->curVolumeStatus = DUMP_NOTHING;
248 fromconn = Bind(htonl(curDump->hostAddr)); /* get connection to the server */
250 /* Determine when the volume was last cloned and updated */
251 volumeInfo.volEntries_val = (volintInfo *) 0;
252 volumeInfo.volEntries_len = 0;
253 rc = AFSVolListOneVolume(fromconn, curDump->partition, curDump->vid,
257 updatedate = volumeInfo.volEntries_val[0].updateDate;
260 RWVOL) ? time(0) : volumeInfo.volEntries_val[0].creationDate);
262 if (curDump->date >= curDump->cloneDate)
263 ERROR_EXIT(0); /* not recloned since last dump */
264 if (curDump->date > updatedate) {
265 dparamsPtr->curVolumeStatus = DUMP_NODUMP; /* not modified since last dump */
269 /* Start the volserver transaction and dump */
270 rc = AFSVolTransCreate(fromconn, curDump->vid, curDump->partition, ITBusy,
274 fromcall = rx_NewCall(fromconn);
276 rc = StartAFSVolDump(fromcall, fromtid, curDump->date);
280 dparamsPtr->curVolumeStatus = DUMP_PARTIAL;
281 dparamsPtr->curVolStartPos = tapeInfoPtr->position;
283 /* buffer is place in bufferBlock to write volume data.
284 * butm_writeFileData() assumes the previous BUTM_HDRSIZE bytes
285 * is available to write the tape block header.
287 buffer = bufferBlock + BUTM_HDRSIZE;
289 /* Dump one volume fragment at a time until we dump the full volume.
290 * A volume with more than 1 fragment means the volume will 'span'
293 for (fragmentNumber = 1; !endofvolume; fragmentNumber++) { /*frag */
294 rc = butm_WriteFileBegin(tapeInfoPtr);
296 ErrorLog(1, taskId, rc, tapeInfoPtr->error,
297 "Can't write FileBegin on tape\n");
300 indump = 1; /* first write to tape */
302 /* Create and Write the volume header */
303 makeVolumeHeader(&hostVolumeHeader, dparamsPtr, fragmentNumber);
304 hostVolumeHeader.contd = ((fragmentNumber == 1) ? 0 : TC_VOLCONTD);
305 volumeHeader_hton(&hostVolumeHeader, (struct volumeHeader *)buffer);
307 rc = butm_WriteFileData(tapeInfoPtr, buffer, 1,
308 sizeof(hostVolumeHeader));
310 ErrorLog(1, taskId, rc, tapeInfoPtr->error,
311 "Can't write VolumeHeader on tape\n");
315 bytesWritten = BUTM_BLOCKSIZE; /* Wrote one tapeblock */
316 tsize += bytesWritten;
318 /* Start reading volume data, rx_Read(), and dumping to the tape
319 * until we've dumped the entire volume (endofvolume == 1). We can
320 * exit this loop early if we find we are close to the end of the
321 * tape; in which case we dump the next fragment on the next tape.
326 while (!endofvolume && !fragmentvolume) { /*w */
329 /* Check for abort in the middle of writing data */
330 if (volBytesRead >= chunkSize) {
331 chunkSize += BIGCHUNK;
332 if (checkAbortByTaskId(taskId))
333 ABORT_EXIT(TC_ABORTEDBYREQUEST);
335 /* set bytes dumped for backup */
337 nodePtr->statusNodePtr->nKBytes = tapeInfoPtr->kBytes;
341 /* Determine how much data to read in upcoming RX_Read() call */
343 /* Check if we are close to the EOT. There should at least be some
344 * data on the tape before it is switched. HACK: we have to split a
345 * volume across tapes because the volume trailer says the dump
346 * continues on the next tape (and not the filemark). This could
347 * result in a volume starting on one tape (no volume data dumped) and
348 * continued on the next tape. It'll work, just requires restore to
349 * switch tapes. This allows many small volumes (<16K) to be dumped.
351 kRemaining = butm_remainingKSpace(tapeInfoPtr);
352 if ((kRemaining < tc_KEndMargin)
354 || (tapeInfoPtr->position > (isafile ? 3 : 2)))) {
359 /* Guess at how much data to read. So we don't write off end of tape */
360 if (kRemaining < (tapeblocks * 16)) {
361 if (kRemaining < 0) {
362 toread = BUTM_BLKSIZE;
364 toread = ((kRemaining / 16) + 1) * BUTM_BLKSIZE;
365 if (toread > dataSize)
371 /* Set aside space for the trailing volume header when using large buffers. */
372 if (XBSAMAXBUFFER < toread + sizeof(hostVolumeHeader)) {
373 toread = XBSAMAXBUFFER - sizeof(hostVolumeHeader);
377 /* Read some volume data. */
378 if (fragmentvolume) {
381 bytesread = rx_Read(fromcall, buffer, toread);
382 volBytesRead += bytesread;
383 if (bytesread != toread) {
384 /* Make sure were at end of volume and not a communication error */
385 rc = rx_Error(fromcall);
392 if (fragmentvolume || endofvolume) {
393 /* Create a volume trailer appending it to this data block */
394 makeVolumeHeader(&hostVolumeHeader, dparamsPtr,
396 hostVolumeHeader.contd = (endofvolume ? 0 : TC_VOLCONTD);
397 hostVolumeHeader.magic = TC_VOLENDMAGIC;
398 hostVolumeHeader.endTime = (endofvolume ? time(0) : 0);
399 volumeHeader_hton(&hostVolumeHeader, (struct volumeHeader *)&buffer[bytesread]);
400 bytesread += sizeof(hostVolumeHeader);
403 /* Write the datablock out */
404 /* full data buffer - write it to tape */
405 rc = butm_WriteFileData(tapeInfoPtr, buffer, tapeblocks,
408 ErrorLog(1, taskId, rc, tapeInfoPtr->error,
409 "Can't write VolumeData on tape\n");
412 bytesWritten = tapeblocks * BUTM_BLOCKSIZE;
413 tsize += bytesWritten;
415 /* Display a status line every statusSize or at end of volume */
417 && ((tsize >= statuscount) || endofvolume
418 || fragmentvolume)) {
421 localtime_r(&t, &tm);
422 printf("%02d:%02d:%02d: Task %u: %u KB: %s: %u B\n",
423 tm.tm_hour, tm.tm_min, tm.tm_sec, taskId,
424 tapeInfoPtr->kBytes, hostVolumeHeader.volumeName,
426 statuscount = tsize + statusSize;
430 /* End the dump before recording it in BUDB as successfully dumped */
431 rc = butm_WriteFileEnd(tapeInfoPtr);
434 ErrorLog(1, taskId, rc, tapeInfoPtr->error,
435 "Can't write FileEnd on tape\n");
439 /* Record in BUDB the volume fragment as succcessfully dumped */
440 volumeFlags = ((fragmentNumber == 1) ? BUDB_VOL_FIRSTFRAG : 0);
442 volumeFlags |= BUDB_VOL_LASTFRAG;
443 rc = addVolume(0, dparamsPtr->databaseDumpId, dparamsPtr->tapeName,
444 nodePtr->dumps[dparamsPtr->curVolume].name,
445 nodePtr->dumps[dparamsPtr->curVolume].vid,
446 nodePtr->dumps[dparamsPtr->curVolume].cloneDate,
447 dparamsPtr->curVolStartPos, volBytesRead,
448 (fragmentNumber - 1), volumeFlags);
452 /* If haven't finished dumping the volume, end this
453 * tape and get the next tape.
456 /* Write an EOT marker.
457 * Log the error but ignore it since the dump is effectively done.
458 * Scantape will detect continued volume and not read the EOT.
460 rc = butm_WriteEOT(tapeInfoPtr);
462 TapeLog(1, taskId, rc, tapeInfoPtr->error,
463 "Warning: Can't write End-Of-Dump on tape\n");
465 /* Unmount the tape */
466 unmountTape(taskId, tapeInfoPtr);
468 /* Tell the database the tape is complete (and ok) */
469 rc = finishTape(&dparamsPtr->tape,
470 dparamsPtr->tapeInfoPtr->kBytes +
471 (dparamsPtr->tapeInfoPtr->nBytes ? 1 : 0));
475 /* get the next tape. Prompt, mount, and add it into the database */
476 dparamsPtr->tapeSeq++;
477 rc = getDumpTape(dparamsPtr, 1, 0); /* interactive - no append */
481 dparamsPtr->curVolStartPos = tapeInfoPtr->position;
485 dparamsPtr->curVolumeStatus = DUMP_SUCCESS;
489 * If we hit the end, see if this is the first volume on the tape or not.
490 * Also, mark the tape as finished if the tape contains other dumps.
495 ErrorLog(2, taskId, code, tapeInfoPtr->error,
496 "Warning: Dump (%s) hit end-of-tape inferred\n",
497 nodePtr->dumpSetName);
499 if (tapeInfoPtr->position == 2) {
500 dparamsPtr->curVolumeStatus = DUMP_NORETRYEOT;
502 dparamsPtr->curVolumeStatus = DUMP_RETRY;
503 rc = finishTape(&dparamsPtr->tape,
504 dparamsPtr->tapeInfoPtr->kBytes +
505 (dparamsPtr->tapeInfoPtr->nBytes ? 1 : 0));
512 * This is used when an error occurs part way into a volume dump. Clean
513 * the tape state by writing an FileEnd mark. Forgo this action if we hit
517 rc = butm_WriteFileEnd(tapeInfoPtr);
520 ErrorLog(1, taskId, rc, tapeInfoPtr->error,
521 "Can't write FileEnd on tape\n");
526 rc = rx_EndCall(fromcall, 0);
533 rc = AFSVolEndTrans(fromconn, fromtid, &rcode);
535 code = (rc ? rc : rcode);
541 dparamsPtr->curVolumeStatus = DUMP_FAILED;
546 xbsaDumpVolume(struct tc_dumpDesc * curDump, struct dumpRock * dparamsPtr)
549 struct butm_tapeInfo *tapeInfoPtr = dparamsPtr->tapeInfoPtr;
550 struct dumpNode *nodePtr = dparamsPtr->node;
551 char *buffer = bufferBlock;
552 afs_int32 taskId = nodePtr->taskID;
553 afs_int32 rc, code = 0;
555 afs_uint32 volBytesRead;
556 afs_uint32 chunkSize;
557 afs_int32 bytesread; /* rx reads */
558 int endofvolume = 0; /* Have we read all volume data */
559 int begindump = 0, indump = 0; /* if dump transaction started; if dumping data */
560 struct volumeHeader hostVolumeHeader;
562 struct rx_call *fromcall = (struct rx_call *)0;
563 struct rx_connection *fromconn;
564 afs_int32 updatedate, fromtid = 0;
565 volEntries volumeInfo;
566 afs_int32 bytesWritten;
567 afs_uint32 statuscount = statusSize, tsize = 0, esize;
570 char dumpIdStr[XBSA_MAX_OSNAME];
571 char volumeNameStr[XBSA_MAX_PATHNAME];
572 static char *dumpDescription = "AFS volume dump";
573 static char *objectDescription = "XBSA - butc";
575 dparamsPtr->curVolumeStatus = DUMP_NOTHING;
577 fromconn = Bind(htonl(curDump->hostAddr)); /* get connection to the server */
579 /* Determine when the volume was last cloned and updated */
580 volumeInfo.volEntries_val = (volintInfo *) 0;
581 volumeInfo.volEntries_len = 0;
582 rc = AFSVolListOneVolume(fromconn, curDump->partition, curDump->vid,
586 updatedate = volumeInfo.volEntries_val[0].updateDate;
589 RWVOL) ? time(0) : volumeInfo.volEntries_val[0].creationDate);
591 /* Get the volume size (in KB) and increase by 25%. Then set as a hyper */
592 esize = volumeInfo.volEntries_val[0].size;
593 esize += (esize / 4) + 1;
595 if (curDump->date >= curDump->cloneDate)
596 ERROR_EXIT(0); /* not recloned since last dump */
597 if (curDump->date > updatedate) {
598 dparamsPtr->curVolumeStatus = DUMP_NODUMP; /* not modified since last dump */
602 /* Start a new XBSA Transaction */
603 rc = xbsa_BeginTrans(&butxInfo);
604 if (rc != XBSA_SUCCESS) {
605 ErrorLog(1, taskId, rc, 0, "Unable to create a new transaction\n");
608 begindump = 1; /* Will need to do an xbsa_EndTrans */
610 /* Start the volserver transaction and dump. Once started, the
611 * volume status is "partial dump". Also, the transaction with
612 * the volserver is idle until the first read. An idle transaction
613 * will time out in 600 seconds. After the first rx_Read,
614 * the transaction is not idle. See GCTrans().
616 rc = AFSVolTransCreate(fromconn, curDump->vid, curDump->partition, ITBusy,
620 fromcall = rx_NewCall(fromconn);
622 rc = StartAFSVolDump(fromcall, fromtid, curDump->date);
626 dparamsPtr->curVolumeStatus = DUMP_PARTIAL;
627 dparamsPtr->curVolStartPos = tapeInfoPtr->position;
629 /* Tell XBSA what the name and size of volume to write */
630 strcpy(dumpIdStr, butcdumpIdStr); /* "backup_afs_volume_dumps" */
631 sprintf(volumeNameStr, "/%d", dparamsPtr->databaseDumpId);
632 strcat(volumeNameStr, "/");
633 strcat(volumeNameStr, curDump->name); /* <dumpid>/<volname> */
634 hset32(estSize, esize);
635 hshlft(estSize, 10); /* Multiply by 1024 so its in KB */
637 rc = xbsa_WriteObjectBegin(&butxInfo, dumpIdStr, volumeNameStr,
638 xbsalGName, estSize, dumpDescription,
640 if (rc != XBSA_SUCCESS) {
641 ErrorLog(1, taskId, rc, 0,
642 "Unable to begin writing of the fileset data to the server\n");
645 indump = 1; /* Will need to do an xbsa_WriteObjectEnd */
647 /* Create and Write the volume header */
648 makeVolumeHeader(&hostVolumeHeader, dparamsPtr, 1);
649 hostVolumeHeader.contd = 0;
650 volumeHeader_hton(&hostVolumeHeader, (struct volumeHeader *)buffer);
652 rc = xbsa_WriteObjectData(&butxInfo, (struct volumeHeader *)buffer,
653 sizeof(struct volumeHeader), &bytesWritten);
654 if (rc != XBSA_SUCCESS) {
655 ErrorLog(1, taskId, rc, 0,
656 "Unable to write VolumeHeader data to the server\n");
659 /* There is a bug in the ADSM library where the bytesWritten is
660 * not filled in, so we set it as correct anyway.
662 bytesWritten = sizeof(struct volumeHeader);
663 if (bytesWritten != sizeof(struct volumeHeader)) {
664 ErrorLog(1, taskId, rc, 0,
665 "The size of VolumeHeader written (%d) does not equal its actual size (%d)\n",
666 bytesWritten, sizeof(struct volumeHeader));
667 ERROR_EXIT(TC_INTERNALERROR);
670 incSize(tapeInfoPtr, sizeof(struct volumeHeader)); /* Increment amount we've written */
671 tsize += bytesWritten;
673 /* Start reading volume data, rx_Read(), and dumping to the tape
674 * until we've dumped the entire volume (endofvolume == 1).
678 while (!endofvolume) { /*w */
681 /* Check for abort in the middle of writing data */
682 if (volBytesRead >= chunkSize) {
683 chunkSize += BIGCHUNK;
684 if (checkAbortByTaskId(taskId))
685 ABORT_EXIT(TC_ABORTEDBYREQUEST);
687 /* set bytes dumped for backup */
689 nodePtr->statusNodePtr->nKBytes = tapeInfoPtr->kBytes;
693 /* Determine how much data to read in upcoming RX_Read() call */
696 /* Read some volume data. */
697 bytesread = rx_Read(fromcall, buffer, toread);
698 volBytesRead += bytesread;
699 if (bytesread != toread) {
702 /* Make sure were at end of volume and not a communication error */
703 rc = rx_Error(fromcall);
709 /* Create a volume trailer appending it to this data block (if not XBSA) */
710 makeVolumeHeader(&hostVolumeHeader, dparamsPtr, 1);
711 hostVolumeHeader.contd = 0;
712 hostVolumeHeader.magic = TC_VOLENDMAGIC;
713 hostVolumeHeader.endTime = time(0);
714 volumeHeader_hton(&hostVolumeHeader, &buffer[bytesread]);
715 bytesread += sizeof(hostVolumeHeader);
717 /* End the dump and transaction with the volserver. We end it now, before
718 * we make the XBSA call because if XBSA blocks, we could time out on the
719 * volserver (After last read, the transaction with the volserver is idle).
721 rc = rx_EndCall(fromcall, 0);
726 rc = AFSVolEndTrans(fromconn, fromtid, &rcode);
732 /* Write the datablock out */
733 rc = xbsa_WriteObjectData(&butxInfo, buffer, bytesread,
735 if (rc != XBSA_SUCCESS) {
736 ErrorLog(1, taskId, rc, 0,
737 "Unable to write data to the server\n");
740 /* There is a bug in the ADSM library where the bytesWritten is
741 * not filled in, so we set it as correct anyway.
743 bytesWritten = bytesread;
744 if (bytesWritten != bytesread) {
745 ErrorLog(1, taskId, rc, 0,
746 "The size of data written (%d) does not equal size read (%d)\n",
747 bytesWritten, bytesread);
748 ERROR_EXIT(TC_INTERNALERROR);
751 incSize(tapeInfoPtr, bytesread); /* Increment amount we've written */
752 tsize += bytesWritten;
754 /* Display a status line every statusSize or at end of volume */
755 if (statusSize && ((tsize >= statuscount) || endofvolume)) {
758 localtime_r(&t, &tm);
759 printf("%02d:%02d:%02d: Task %u: %u KB: %s: %u B\n", tm.tm_hour,
760 tm.tm_min, tm.tm_sec, taskId, tapeInfoPtr->kBytes,
761 hostVolumeHeader.volumeName, tsize);
762 statuscount = tsize + statusSize;
766 /* End the XBSA transaction before recording it in BUDB as successfully dumped */
767 rc = xbsa_WriteObjectEnd(&butxInfo);
769 if (rc != XBSA_SUCCESS) {
770 ErrorLog(1, taskId, rc, 0,
771 "Unable to terminate writing of the volume data to the server");
774 rc = xbsa_EndTrans(&butxInfo);
776 tapeInfoPtr->position++;
777 if (rc != XBSA_SUCCESS) {
778 ErrorLog(1, taskId, rc, 0,
779 "Unable to terminate the current transaction");
783 /* Record in BUDB the volume fragment as succcessfully dumped */
784 rc = addVolume(0, dparamsPtr->databaseDumpId, dparamsPtr->tapeName,
785 nodePtr->dumps[dparamsPtr->curVolume].name,
786 nodePtr->dumps[dparamsPtr->curVolume].vid,
787 nodePtr->dumps[dparamsPtr->curVolume].cloneDate,
788 dparamsPtr->curVolStartPos, volBytesRead, 0 /*frag0 */ ,
789 (BUDB_VOL_FIRSTFRAG | BUDB_VOL_LASTFRAG));
793 dparamsPtr->curVolumeStatus = DUMP_SUCCESS;
796 /* Cleanup after an error occurs part way into a volume dump */
798 rc = rx_EndCall(fromcall, 0);
805 rc = AFSVolEndTrans(fromconn, fromtid, &rcode);
807 code = (rc ? rc : rcode);
810 /* If this dump failed, what happens to successive retries
811 * of the volume? How do they get recorded in the XBSA database
812 * (overwritten)? If not, we don't record this in the BUDB database
813 * so it will not be removed when we delete the dump. What to do?
814 * Also if the volume was never recorded in the DB (partial dump).
818 rc = xbsa_WriteObjectEnd(&butxInfo);
820 if (rc != XBSA_SUCCESS) {
821 ErrorLog(1, taskId, rc, 0,
822 "Unable to terminate writing of the volume data to the server");
824 tapeInfoPtr->position++;
828 /* End the XBSA Transaction */
829 rc = xbsa_EndTrans(&butxInfo);
831 if (rc != XBSA_SUCCESS) {
832 ErrorLog(1, taskId, rc, 0,
833 "Unable to terminate the current transaction");
840 dparamsPtr->curVolumeStatus = DUMP_FAILED;
847 #define HOSTADDR(sockaddr) (sockaddr)->sin_addr.s_addr
850 * Go through the list of volumes to dump, dumping each one. The action
851 * taken when a volume dump fails, depends on the passNumber. At minimum,
852 * the failed volume is remembered.
854 * flushSavedEntries - inconsistent treatment for errors. What should
855 * be done for user aborts?
859 dumpPass(struct dumpRock * dparamsPtr, int passNumber)
861 struct dumpNode *nodePtr = dparamsPtr->node;
862 struct butm_tapeInfo *tapeInfoPtr = dparamsPtr->tapeInfoPtr;
863 afs_int32 taskId = nodePtr->taskID;
864 struct tc_dumpDesc *curDump;
866 afs_int32 code = 0, tcode, dvcode;
868 struct vldbentry vldbEntry;
869 struct sockaddr_in server;
872 TapeLog(2, taskId, 0, 0, "Starting pass %d\n", passNumber);
874 /* while there are more volumes to dump */
875 for (dparamsPtr->curVolume = 0; dparamsPtr->curVolume < nodePtr->arraySize; dparamsPtr->curVolume++) { /*w */
876 curDump = &nodePtr->dumps[dparamsPtr->curVolume];
877 if (curDump->hostAddr == 0)
880 /* set name of current volume being dumped */
882 strcpy(nodePtr->statusNodePtr->volumeName, curDump->name);
885 /* Determine location of the volume.
886 * In case the volume moved has moved.
888 if (passNumber > 1) { /*pass */
890 bc_GetEntryByID(cstruct, curDump->vid, curDump->vtype,
893 ErrorLog(0, taskId, tcode, 0,
894 "Volume %s (%u) failed - Can't find volume in VLDB\n",
895 curDump->name, curDump->vid);
896 curDump->hostAddr = 0;
897 dparamsPtr->volumesFailed++;
901 switch (curDump->vtype) {
903 if (!(vldbEntry.flags & BACK_EXISTS)) {
904 ErrorLog(0, taskId, 0, 0,
905 "Volume %s (%u) failed - Backup volume no longer exists\n",
906 curDump->name, curDump->vid);
907 curDump->hostAddr = 0;
908 dparamsPtr->volumesFailed++;
911 /* Fall into RWVOL case */
914 for (e = 0; e < vldbEntry.nServers; e++) { /* Find the RW volume */
915 if (vldbEntry.serverFlags[e] & ITSRWVOL)
921 /* Try to use the server and partition we found the volume on
922 * Otherwise, use the first RO volume.
924 for (e = 0; e < vldbEntry.nServers; e++) { /* Find the RO volume */
925 if ((curDump->hostAddr == vldbEntry.serverNumber[e])
926 && (curDump->partition ==
927 vldbEntry.serverPartition[e]))
931 if (e >= vldbEntry.nServers) { /* Didn't find RO volume */
932 for (e = 0; e < vldbEntry.nServers; e++) { /* Find the first RO volume */
933 if (vldbEntry.serverFlags[e] & ITSROVOL)
940 ErrorLog(0, taskId, 0, 0,
941 "Volume %s (%u) failed - Unknown volume type\n",
942 curDump->name, curDump->vid);
943 curDump->hostAddr = 0;
948 if (e >= vldbEntry.nServers) {
949 ErrorLog(0, taskId, 0, 0,
950 "Volume %s (%u) failed - Can't find volume entry in VLDB\n",
951 curDump->name, curDump->vid);
952 curDump->hostAddr = 0;
953 dparamsPtr->volumesFailed++;
957 /* Remember the server and partition the volume exists on */
958 memset(&server, 0, sizeof(server));
959 server.sin_addr.s_addr = vldbEntry.serverNumber[e];
961 server.sin_family = AF_INET;
962 #ifdef STRUCT_SOCKADDR_HAS_SA_LEN
963 server.sin_len = sizeof(struct sockaddr_in);
965 curDump->hostAddr = HOSTADDR(&server);
966 curDump->partition = vldbEntry.serverPartition[e];
968 /* Determine date from which to do an incremental dump
970 if (nodePtr->parent) {
972 bcdb_FindClone(nodePtr->parent, curDump->name,
977 curDump->date = 0; /* do a full dump */
981 if (checkAbortByTaskId(taskId))
982 ERROR_EXIT(TC_ABORTEDBYREQUEST);
984 /* Establish connection to volume - UV_ routine expects
985 * host address in network order
988 dvcode = xbsaDumpVolume(curDump, dparamsPtr);
990 dvcode = dumpVolume(curDump, dparamsPtr);
992 action = dparamsPtr->curVolumeStatus;
994 /* Flush volume and tape entries to the database */
995 tcode = flushSavedEntries(action);
1001 TapeLog(1, taskId, 0, 0, "Volume %s (%u) successfully dumped\n",
1002 curDump->name, curDump->vid);
1004 ErrorLog(1, taskId, dvcode, 0,
1005 "Warning: Termination processing error on volume %s (%u)\n",
1006 curDump->name, curDump->vid);
1008 curDump->hostAddr = 0;
1009 dparamsPtr->volumesDumped++;
1014 if (action == DUMP_PARTIAL) {
1015 ErrorLog(1, taskId, dvcode, 0,
1016 "Volume %s (%u) failed - partially dumped\n",
1017 curDump->name, curDump->vid);
1018 } else if (dvcode) {
1019 ErrorLog(0, taskId, dvcode, 0, "Volume %s (%u) failed\n",
1020 curDump->name, curDump->vid);
1022 ErrorLog(0, taskId, dvcode, 0,
1023 "Volume %s (%u) not dumped - has not been re-cloned since last dump\n",
1024 curDump->name, curDump->vid);
1027 if (passNumber == maxpass) {
1031 ch = retryPrompt(curDump->name, curDump->vid, taskId);
1034 case 'r': /* retry */
1035 dparamsPtr->curVolume--; /* redump this volume */
1038 case 'o': /* omit */
1039 ErrorLog(1, taskId, 0, 0, "Volume %s (%u) omitted\n",
1040 curDump->name, curDump->vid);
1041 dparamsPtr->volumesFailed++;
1043 case 'a': /* abort */
1044 TapeLog(1, taskId, 0, 0, "Dump aborted\n");
1045 ERROR_EXIT(TC_ABORTEDBYREQUEST);
1048 ERROR_EXIT(TC_INTERNALERROR);
1055 TapeLog(1, taskId, dvcode, 0,
1056 "Volume %s (%u) hit end-of-tape inferred - will retry on next tape\n",
1057 curDump->name, curDump->vid);
1059 /* Get the next tape */
1060 unmountTape(taskId, tapeInfoPtr);
1062 dparamsPtr->tapeSeq++;
1063 tcode = getDumpTape(dparamsPtr, 1, 0); /* interactive - no appends */
1067 dparamsPtr->curVolume--; /* redump this volume */
1071 case DUMP_NORETRYEOT:
1072 ErrorLog(1, taskId, 0, 0,
1073 "Volume %s (%u) failed - volume larger than tape\n",
1074 curDump->name, curDump->vid);
1076 /* rewrite the label on the tape - rewind - no need to switch tapes */
1077 tcode = butm_Create(tapeInfoPtr, &dparamsPtr->tapeLabel, 1);
1079 ErrorLog(0, taskId, tcode, tapeInfoPtr->error,
1080 "Can't relabel tape\n");
1082 unmountTape(taskId, tapeInfoPtr);
1083 tcode = getDumpTape(dparamsPtr, 1, 0); /* interactive - no appends */
1086 } else { /* Record the tape in database */
1087 tapepos = tapeInfoPtr->position;
1089 useTape(&dparamsPtr->tape, dparamsPtr->databaseDumpId,
1090 dparamsPtr->tapeName,
1091 (dparamsPtr->tapeSeq + dparamsPtr->dump.tapes.b),
1092 dparamsPtr->tapeLabel.useCount,
1093 dparamsPtr->tapeLabel.creationTime,
1094 dparamsPtr->tapeLabel.expirationDate, tapepos);
1097 curDump->hostAddr = 0;
1098 dparamsPtr->volumesFailed++;
1102 TapeLog(1, taskId, dvcode, 0,
1103 "Volume %s (%u) not dumped - has not been modified since last dump\n",
1104 curDump->name, curDump->vid);
1106 curDump->hostAddr = 0;
1107 dparamsPtr->volumesNotDumped++;
1111 ErrorLog(1, taskId, dvcode, 0, "Volume %s (%u) failed\n",
1112 curDump->name, curDump->vid);
1119 /* check if we terminated while processing a volume */
1120 if (dparamsPtr->curVolume < nodePtr->arraySize) {
1121 TapeLog(2, taskId, 0, 0,
1122 "Terminated while processing Volume %s (%u)\n", curDump->name,
1126 /* print a summary of this pass */
1127 TapeLog(2, taskId, 0, 0, "End of pass %d: Volumes remaining = %d\n",
1129 nodePtr->arraySize - (dparamsPtr->volumesDumped +
1130 dparamsPtr->volumesFailed +
1131 dparamsPtr->volumesNotDumped));
1138 struct dumpNode *nodePtr = (struct dumpNode *)param;
1139 struct dumpRock dparams;
1140 struct butm_tapeInfo tapeInfo;
1146 /* for volume setup */
1148 int failedvolumes = 0;
1149 int dumpedvolumes = 0;
1150 int nodumpvolumes = 0;
1153 char finishedMsg1[50];
1154 char finishedMsg2[50];
1155 time_t startTime = 0;
1157 afs_int32 allocbufferSize;
1159 extern struct deviceSyncNode *deviceLatch;
1160 extern struct tapeConfig globalTapeConfig;
1162 taskId = nodePtr->taskID; /* Get task Id */
1163 setStatus(taskId, DRIVE_WAIT);
1164 EnterDeviceQueue(deviceLatch);
1165 clearStatus(taskId, DRIVE_WAIT);
1168 TapeLog(2, taskId, 0, 0, "Dump %s\n", nodePtr->dumpSetName);
1170 /* setup the dump parameters */
1171 memset(&dparams, 0, sizeof(dparams));
1172 dparams.node = nodePtr;
1173 dparams.tapeInfoPtr = &tapeInfo;
1174 dlqInit(&savedEntries);
1177 /* Instantiate the tape module */
1178 tapeInfo.structVersion = BUTM_MAJORVERSION;
1179 code = butm_file_Instantiate(&tapeInfo, &globalTapeConfig);
1181 ErrorLog(0, taskId, code, tapeInfo.error,
1182 "Can't initialize the tape module\n");
1187 /* check if abort requested while waiting on device latch */
1188 if (checkAbortByTaskId(taskId))
1189 ERROR_EXIT(TC_ABORTEDBYREQUEST);
1191 /* Are there volumes to dump */
1192 if (nodePtr->arraySize == 0) {
1193 TLog(taskId, "Dump (%s), no volumes to dump\n", nodePtr->dumpSetName);
1197 /* Allocate a buffer for the dumps. Leave room for header and vol-trailer.
1198 * dataSize is amount of data to read in each rx_Read() call.
1201 /* XBSA dumps have not header */
1202 dataSize = BufferSize;
1203 allocbufferSize = dataSize + sizeof(struct volumeHeader);
1205 tapeblocks = BufferSize / BUTM_BLOCKSIZE; /* # of 16K tapeblocks */
1206 dataSize = (tapeblocks * BUTM_BLKSIZE);
1208 BUTM_HDRSIZE + dataSize + sizeof(struct volumeHeader);
1211 bufferBlock = malloc(allocbufferSize);
1213 ErrorLog(0, taskId, TC_NOMEMORY, 0,
1214 "Can't allocate BUFFERSIZE for dumps\n");
1215 ERROR_EXIT(TC_NOMEMORY);
1218 /* Determine the dumpid of the most recent dump of this volumeset and dumplevel
1219 * Used when requesting a tape. Done now because once we create the dump, the
1220 * routine will then find the newly created dump.
1222 sprintf(strlevel, "%d", nodePtr->level);
1224 bcdb_FindLatestDump(nodePtr->volumeSetName, strlevel,
1227 if (code != BUDB_NODUMPNAME) {
1228 ErrorLog(0, taskId, code, 0, "Can't read backup database\n");
1231 memset(&dparams.lastDump, 0, sizeof(dparams.lastDump));
1234 code = createDump(&dparams); /* enter dump into database */
1236 ErrorLog(0, taskId, code, 0, "Can't create dump in database\n");
1240 TLog(taskId, "Dump %s (DumpID %u)\n", nodePtr->dumpSetName,
1241 dparams.databaseDumpId);
1244 /* mount the tape and write its label */
1245 code = getDumpTape(&dparams, autoQuery, nodePtr->doAppend);
1247 /* Create a dummy tape to satisfy backup databae */
1248 code = getXBSATape(&dparams);
1249 tapeInfo.position = 1;
1252 /* If didn't write the label, remove dump from the database */
1253 if (!dparams.wroteLabel) {
1254 i = bcdb_deleteDump(dparams.databaseDumpId, 0, 0, 0);
1255 if (i && (i != BUDB_NOENT))
1256 ErrorLog(1, taskId, i, 0,
1257 "Warning: Can't delete dump %u from database\n",
1258 dparams.databaseDumpId);
1260 dparams.databaseDumpId = 0;
1262 ERROR_EXIT(code); /* exit with code from getTape */
1265 startTime = time(0);
1266 for (pass = 1; pass <= maxpass; pass++) {
1267 lastPass = (pass == maxpass);
1268 code = dumpPass(&dparams, pass);
1272 /* if no failed volumes, we're done */
1273 if ((dparams.volumesDumped + dparams.volumesFailed +
1274 dparams.volumesNotDumped) == nodePtr->arraySize)
1279 * Log the error but ignore it since the dump is effectively done.
1280 * Scantape may assume another volume and ask for next tape.
1283 code = butm_WriteEOT(&tapeInfo);
1285 TapeLog(0, taskId, code, tapeInfo.error,
1286 "Warning: Can't write end-of-dump on tape\n");
1290 finishTape(&dparams.tape,
1291 dparams.tapeInfoPtr->kBytes +
1292 (dparams.tapeInfoPtr->nBytes ? 1 : 0));
1296 code = finishDump(&dparams.dump);
1300 action = dparams.curVolumeStatus;
1301 code = flushSavedEntries(action);
1312 unmountTape(taskId, &tapeInfo);
1316 dumpedvolumes = dparams.volumesDumped;
1317 nodumpvolumes = dparams.volumesNotDumped;
1318 failedvolumes = nodePtr->arraySize - (dumpedvolumes + nodumpvolumes);
1320 /* pass back the number of volumes we failed to dump */
1322 nodePtr->statusNodePtr->volsFailed = failedvolumes;
1325 lastPass = 1; /* In case we aborted */
1327 DUMPNAME(finishedMsg1, nodePtr->dumpSetName, dparams.databaseDumpId);
1328 sprintf(finishedMsg2, "%d volumes dumped", dumpedvolumes);
1329 if (failedvolumes) {
1330 sprintf(msg, ", %d failed", failedvolumes);
1331 strcat(finishedMsg2, msg);
1333 if (nodumpvolumes) {
1334 sprintf(msg, ", %d unchanged", nodumpvolumes);
1335 strcat(finishedMsg2, msg);
1338 if (code == TC_ABORTEDBYREQUEST) {
1339 ErrorLog(0, taskId, 0, 0, "%s: Aborted by request. %s\n",
1340 finishedMsg1, finishedMsg2);
1341 clearStatus(taskId, ABORT_REQUEST);
1342 setStatus(taskId, ABORT_DONE);
1344 ErrorLog(0, taskId, code, 0, "%s: Finished with errors. %s\n",
1345 finishedMsg1, finishedMsg2);
1346 setStatus(taskId, TASK_ERROR);
1348 TLog(taskId, "%s: Finished. %s\n", finishedMsg1, finishedMsg2);
1352 /* Record how long the dump took */
1353 if (centralLogIO && startTime) {
1355 afs_int32 hrs, min, sec, tmp;
1357 struct tm tmstart, tmend;
1359 localtime_r(&startTime, &tmstart);
1360 localtime_r(&endTime, &tmend);
1361 timediff = (int)endTime - (int)startTime;
1362 hrs = timediff / 3600;
1363 tmp = timediff % 3600;
1368 "%-5d %02d/%02d/%04d %02d:%02d:%02d "
1369 "%02d/%02d/%04d %02d:%02d:%02d " "%02d:%02d:%02d "
1370 "%s %d of %d volumes dumped (%lu KB)\n", taskId,
1371 tmstart.tm_mon + 1, tmstart.tm_mday, tmstart.tm_year + 1900,
1372 tmstart.tm_hour, tmstart.tm_min, tmstart.tm_sec,
1373 tmend.tm_mon + 1, tmend.tm_mday, tmend.tm_year + 1900,
1374 tmend.tm_hour, tmend.tm_min, tmend.tm_sec, hrs, min, sec,
1375 nodePtr->volumeSetName, dumpedvolumes,
1376 dumpedvolumes + failedvolumes,
1377 afs_printable_uint32_lu(dparams.tapeInfoPtr->kBytes + 1));
1379 fwrite(line, strlen(line), 1, centralLogIO);
1380 fflush(centralLogIO);
1383 setStatus(taskId, TASK_DONE);
1385 FreeNode(taskId); /* free the dump node */
1386 LeaveDeviceQueue(deviceLatch);
1387 return (void *)(intptr_t)(code);
1390 #define BELLTIME 60 /* 60 seconds before a bell rings */
1391 #define BELLCHAR 7 /* ascii for bell */
1394 * prompt the user to decide how to handle a failed volume dump. The
1395 * volume parameters describe the volume that failed
1397 * volumeName - name of volume
1398 * volumeId - volume id
1399 * taskId - for job contrl
1401 * character typed by user, one of r, o or a
1405 retryPrompt(char *volumeName, afs_int32 volumeId, afs_uint32 taskId)
1411 setStatus(taskId, OPR_WAIT);
1412 printf("\nDump of volume %s (%u) failed\n\n", volumeName, volumeId);
1414 printf("Please select action to be taken for this volume\n");
1417 printf("r - retry, try dumping this volume again\n");
1418 printf("o - omit, this volume from this dump\n");
1419 printf("a - abort, the entire dump\n");
1428 #ifdef AFS_PTHREAD_ENV
1429 code = GetResponseKey(5, &ch); /* ch stores key pressed */
1431 code = LWP_GetResponseKey(5, &ch); /* ch stores key pressed */
1434 break; /* input is available */
1436 if (checkAbortByTaskId(taskId)) {
1437 clearStatus(taskId, OPR_WAIT);
1439 ("This tape operation has been aborted by the coordinator\n");
1443 if (time(0) > start + BELLTIME)
1446 /* otherwise, we should beep again, check for abort and go back,
1447 * since the GetResponseKey() timed out.
1450 break; /* input is available */
1452 clearStatus(taskId, OPR_WAIT);
1453 if (ch != 'r' && ch != 'o' && ch != 'a') {
1454 printf("Please select one of the 3 options, r, o or a\n");
1461 /* For testing: it prints the tape label */
1463 printTapeLabel(struct butm_tapeLabel *tl)
1465 printf("Tape Label\n");
1466 printf(" structVersion = %d\n", tl->structVersion);
1467 printf(" creationTime = %u\n", tl->creationTime);
1468 printf(" expirationDate = %u\n", tl->expirationDate);
1469 printf(" AFSName = %s\n", tl->AFSName);
1470 printf(" cell = %s\n", tl->cell);
1471 printf(" dumpid = %d\n", tl->dumpid);
1472 printf(" useCount = %d\n", tl->useCount);
1473 printf(" comment = %s\n", tl->comment);
1474 printf(" pName = %s\n", tl->pName);
1475 printf(" size = %u\n", tl->size);
1476 printf(" dumpPath = %s\n", tl->dumpPath);
1481 * Create a tape structure to be satisfy the backup database
1482 * even though we don't really use a tape with XBSA.
1485 getXBSATape(struct dumpRock *dparamsPtr)
1487 struct dumpNode *nodePtr = dparamsPtr->node;
1488 struct butm_tapeInfo *tapeInfoPtr = dparamsPtr->tapeInfoPtr;
1489 struct butm_tapeLabel *tapeLabelPtr = &dparamsPtr->tapeLabel;
1492 tc_MakeTapeName(dparamsPtr->tapeName, &nodePtr->tapeSetDesc,
1493 dparamsPtr->tapeSeq);
1495 GetNewLabel(tapeInfoPtr, "" /*pName */ , dparamsPtr->tapeName,
1497 strcpy(tapeLabelPtr->dumpPath, nodePtr->dumpName);
1498 tapeLabelPtr->dumpid = dparamsPtr->databaseDumpId;
1499 tapeLabelPtr->expirationDate =
1500 calcExpirationDate(nodePtr->tapeSetDesc.expType,
1501 nodePtr->tapeSetDesc.expDate, time(0));
1503 /* printTapeLabel(tapeLabelPtr); For testing */
1506 useTape(&dparamsPtr->tape, dparamsPtr->databaseDumpId,
1507 dparamsPtr->tapeName,
1508 (dparamsPtr->tapeSeq + dparamsPtr->dump.tapes.b),
1509 tapeLabelPtr->useCount, tapeLabelPtr->creationTime,
1510 tapeLabelPtr->expirationDate, 0 /*tape position */ );
1515 * iterate until the desired tape (as specified by the dump structures)
1519 * 0 - assume the tape is there. Prompt if assumption false
1520 * 1 - prompt regardless
1524 getDumpTape(struct dumpRock *dparamsPtr, int interactiveFlag,
1527 struct dumpNode *nodePtr = dparamsPtr->node;
1528 struct butm_tapeInfo *tapeInfoPtr = dparamsPtr->tapeInfoPtr;
1529 struct butm_tapeLabel *newTapeLabelPtr = &dparamsPtr->tapeLabel;
1530 char AFSTapeName[TC_MAXTAPENAMELEN];
1531 afs_int32 taskId = nodePtr->taskID;
1532 struct butm_tapeLabel oldTapeLabel;
1533 struct budb_dumpEntry dumpEntry;
1534 struct budb_tapeEntry tapeEntry;
1535 struct budb_volumeEntry volEntry;
1542 afs_int32 tapepos, lastpos;
1544 extern struct tapeConfig globalTapeConfig;
1546 askForTape = interactiveFlag;
1547 dparamsPtr->wroteLabel = 0;
1549 /* Keep prompting for a tape until we get it right */
1551 /* What the name of the tape would be if not appending to it */
1552 tc_MakeTapeName(AFSTapeName, &nodePtr->tapeSetDesc,
1553 dparamsPtr->tapeSeq);
1559 PromptForTape((doAppend ? APPENDOPCODE : WRITEOPCODE),
1560 AFSTapeName, dparamsPtr->databaseDumpId, taskId,
1568 /* open the tape device */
1569 code = butm_Mount(tapeInfoPtr, AFSTapeName);
1571 TapeLog(0, taskId, code, tapeInfoPtr->error, "Can't open tape\n");
1575 /* Read the tape label */
1576 code = butm_ReadLabel(tapeInfoPtr, &oldTapeLabel, 1); /* rewind */
1578 if (tapeInfoPtr->error) {
1579 ErrorLog(0, taskId, code, tapeInfoPtr->error,
1580 "Warning: Tape error while reading label (will proceed with dump)\n");
1582 memset(&oldTapeLabel, 0, sizeof(oldTapeLabel));
1585 /* Check if null tape. Prior 3.3, backup tapes have no dump id */
1586 if ((strcmp(oldTapeLabel.AFSName, "") == 0)
1587 && (oldTapeLabel.dumpid == 0)) {
1590 "Dump not found on tape. Proceeding with initial dump\n");
1593 } else if (doAppend) { /* appending */
1594 /* Check that we don't have a database dump tape */
1595 if (databaseTape(oldTapeLabel.AFSName)) {
1596 char gotName[BU_MAXTAPELEN + 32];
1598 /* label does not match */
1599 LABELNAME(gotName, &oldTapeLabel);
1600 TLog(taskId, "Can't append to database tape %s\n", gotName);
1604 /* Verify that the tape is of version 4 (AFS 3.3) or greater */
1605 if (oldTapeLabel.structVersion < TAPE_VERSION_4) {
1607 "Can't append: requires tape version %d or greater\n",
1612 /* Verify that the last tape of the dump set is in the drive.
1613 * volEntry will be zeroed if last dump has no volume entries.
1616 bcdb_FindLastTape(oldTapeLabel.dumpid, &dumpEntry, &tapeEntry,
1619 ErrorLog(0, taskId, code, 0,
1620 "Can't append: Can't find last volume of dumpId %u in database\n",
1621 oldTapeLabel.dumpid);
1622 printf("Please scan the dump in or choose another tape\n");
1626 (volEntry.position ? volEntry.position : tapeEntry.labelpos);
1628 if (strcmp(TNAME(&oldTapeLabel), tapeEntry.name)) {
1629 char expName[BU_MAXTAPELEN + 32], gotName[BU_MAXTAPELEN + 32];
1631 TAPENAME(expName, tapeEntry.name, oldTapeLabel.dumpid);
1632 LABELNAME(gotName, &oldTapeLabel);
1635 "Can't append: Last tape in dump-set is %s, label seen %s\n",
1640 /* After reading the tape label, we now know what it is */
1641 strcpy(AFSTapeName, oldTapeLabel.AFSName); /* the real name */
1642 strcpy(tapeInfoPtr->name, oldTapeLabel.AFSName); /* the real name */
1644 /* Position after last volume on the tape */
1645 code = butm_SeekEODump(tapeInfoPtr, lastpos);
1647 ErrorLog(0, taskId, code, tapeInfoPtr->error,
1648 "Can't append: Can't position to end of dump on tape %s\n",
1653 /* Track size of tape - set after seek since seek changes the value */
1654 tapeInfoPtr->kBytes = tapeEntry.useKBytes;
1655 } else { /* not appending */
1659 struct budb_dumpEntry de, de2;
1661 /* Check if tape name is not what expected - null tapes are acceptable
1662 * Don't do check if the tape has a user defined label.
1664 if (dump_namecheck && (strcmp(oldTapeLabel.pName, "") == 0)) {
1665 if (strcmp(oldTapeLabel.AFSName, "") && /* not null tape */
1666 strcmp(oldTapeLabel.AFSName, AFSTapeName)) { /* not expected name */
1667 TLog(taskId, "Tape label expected %s, label seen %s\n",
1668 AFSTapeName, oldTapeLabel.AFSName);
1672 /* Check that we don't have a database dump tape */
1673 if (databaseTape(oldTapeLabel.AFSName)) {
1674 /* label does not match */
1676 "Tape label expected %s, can't dump to database tape %s\n",
1677 AFSTapeName, oldTapeLabel.AFSName);
1682 /* Verify the tape has not expired - only check if not appending */
1683 if (!tapeExpired(&oldTapeLabel)) {
1684 TLog(taskId, "This tape has not expired\n");
1688 /* Given a tape dump with good data, verify we don't overwrite recent dumps
1689 * and also verify that the volume will be restorable - if not print warnings
1691 if (oldTapeLabel.dumpid) {
1692 /* Do not overwrite a tape that belongs to the dump's dumpset */
1694 (dparamsPtr->initialDumpId ? dparamsPtr->
1695 initialDumpId : dparamsPtr->databaseDumpId);
1696 if (oldTapeLabel.dumpid == tapeid) {
1697 ErrorLog(0, taskId, 0, 0,
1698 "Can't overwrite tape containing the dump in progress\n");
1702 /* Since the dumpset on this tape will be deleted from database, check if
1703 * any of the dump's parent-dumps are on this tape.
1705 for (dmp = nodePtr->parent; dmp; dmp = de.parent) {
1706 code = bcdb_FindDumpByID(dmp, &de);
1708 ErrorLog(0, taskId, 0, 0,
1709 "Warning: Can't find parent dump %u in backup database\n",
1714 tapeid = (de.initialDumpID ? de.initialDumpID : de.id);
1715 if (oldTapeLabel.dumpid == tapeid) {
1716 ErrorLog(0, taskId, 0, 0,
1717 "Can't overwrite the parent dump %s (DumpID %u)\n",
1723 /* Since the dumpset on this tape will be deleted from database, check if
1724 * any of the dumps in this dumpset are most-recent-dumps.
1726 for (dmp = oldTapeLabel.dumpid; dmp; dmp = de.appendedDumpID) {
1727 if (dmp == dparamsPtr->lastDump.id) {
1728 memcpy(&de, &dparamsPtr->lastDump, sizeof(de));
1729 memcpy(&de2, &dparamsPtr->lastDump, sizeof(de2));
1731 code = bcdb_FindDumpByID(dmp, &de);
1734 sprintf(strlevel, "%d", de.level);
1736 bcdb_FindLatestDump(de.volumeSetName, strlevel,
1742 /* If dump on the tape is the latest dump at this level */
1743 if (de.id == de2.id) {
1744 if (strcmp(DUMP_TAPE_NAME, de2.name) == 0) {
1745 ErrorLog(0, taskId, 0, 0,
1746 "Warning: Overwriting most recent dump %s (DumpID %u)\n",
1749 ErrorLog(0, taskId, 0, 0,
1750 "Warning: Overwriting most recent dump of the '%s' volumeset: %s (DumpID %u)\n",
1751 de.volumeSetName, de.name, de.id);
1755 } /* if (oldTapeLabel.dumpid) */
1756 } /* else not appending */
1759 * Now have the right tape. Create a new label for the tape
1760 * Appended labels have the dump's dumpId - labels at beginnings of
1761 * tape have the initial dump's dumpId.
1762 * Appended labels do not increment the useCount.
1763 * Labels at beginnings of tape use the most future expiration of the dump set.
1765 GetNewLabel(tapeInfoPtr, oldTapeLabel.pName, AFSTapeName,
1767 strcpy(newTapeLabelPtr->dumpPath, nodePtr->dumpName);
1768 newTapeLabelPtr->expirationDate =
1769 calcExpirationDate(nodePtr->tapeSetDesc.expType,
1770 nodePtr->tapeSetDesc.expDate, time(0));
1771 newTapeLabelPtr->dumpid = dparamsPtr->databaseDumpId;
1772 newTapeLabelPtr->useCount = oldTapeLabel.useCount;
1775 newTapeLabelPtr->useCount++;
1776 if (dparamsPtr->initialDumpId) {
1777 newTapeLabelPtr->dumpid = dparamsPtr->initialDumpId;
1778 expir = ExpirationDate(dparamsPtr->initialDumpId);
1779 if (expir > newTapeLabelPtr->expirationDate)
1780 newTapeLabelPtr->expirationDate = expir;
1784 /* write the label on the tape - rewind if not appending and vice-versa */
1785 code = butm_Create(tapeInfoPtr, newTapeLabelPtr, !doAppend);
1787 char gotName[BU_MAXTAPELEN + 32];
1789 LABELNAME(gotName, newTapeLabelPtr);
1790 TapeLog(0, taskId, code, tapeInfoPtr->error,
1791 "Can't label tape as %s\n", gotName);
1794 dparamsPtr->wroteLabel = 1; /* Remember we wrote the label */
1795 tapepos = tapeInfoPtr->position - 1;
1797 strcpy(dparamsPtr->tapeName, TNAME(newTapeLabelPtr));
1799 /* If appending, set dumpentry in the database as appended. */
1801 char gotName[BU_MAXTAPELEN + 32];
1803 nodePtr->tapeSetDesc.b = extractTapeSeq(AFSTapeName);
1804 dparamsPtr->dump.tapes.b = nodePtr->tapeSetDesc.b;
1805 dparamsPtr->initialDumpId = oldTapeLabel.dumpid;
1806 strcpy(nodePtr->tapeSetDesc.format, dumpEntry.tapes.format);
1809 bcdb_MakeDumpAppended(dparamsPtr->databaseDumpId,
1810 dparamsPtr->initialDumpId,
1811 nodePtr->tapeSetDesc.b);
1813 ErrorLog(2, taskId, code, 0,
1814 "Warning: Can't append dump %u to dump %u in database\n",
1815 dparamsPtr->databaseDumpId,
1816 dparamsPtr->initialDumpId);
1818 LABELNAME(gotName, &oldTapeLabel);
1819 TLog(taskId, "Appending dump %s (DumpID %u) to tape %s\n",
1820 nodePtr->dumpSetName, dparamsPtr->databaseDumpId, gotName);
1823 /* If not appending, delete overwritten dump from the database */
1825 if ((oldTapeLabel.structVersion >= TAPE_VERSION_3)
1826 && oldTapeLabel.dumpid) {
1827 code = bcdb_deleteDump(oldTapeLabel.dumpid, 0, 0, 0);
1828 if (code && (code != BUDB_NOENT))
1829 ErrorLog(0, taskId, code, 0,
1830 "Warning: Can't delete old dump %u from database\n",
1831 oldTapeLabel.dumpid);
1836 useTape(&dparamsPtr->tape, dparamsPtr->databaseDumpId,
1837 dparamsPtr->tapeName,
1838 (dparamsPtr->tapeSeq + dparamsPtr->dump.tapes.b),
1839 newTapeLabelPtr->useCount, newTapeLabelPtr->creationTime,
1840 newTapeLabelPtr->expirationDate, tapepos);
1843 * The margin of space to check for end of tape is set to the
1844 * amount of space used to write an end-of-tape multiplied by 2.
1845 * The amount of space is size of a 16K volume trailer, a 16K File
1846 * End mark, its EOF marker, a 16K EODump marker, its EOF marker,
1847 * and up to two EOF markers done on close (3 16K blocks + 4 EOF
1850 tc_EndMargin = (3 * 16384 + 4 * globalTapeConfig.fileMarkSize) * 2;
1851 tc_KEndMargin = tc_EndMargin / 1024;
1855 unmountTape(taskId, tapeInfoPtr);
1863 makeVolumeHeader(struct volumeHeader *vhptr, struct dumpRock *dparamsPtr,
1866 struct dumpNode *nodePtr = dparamsPtr->node;
1867 struct tc_dumpDesc *curDump;
1870 curDump = &nodePtr->dumps[dparamsPtr->curVolume];
1872 memset(vhptr, 0, sizeof(*vhptr));
1873 strcpy(vhptr->volumeName, curDump->name);
1874 vhptr->volumeID = curDump->vid;
1875 vhptr->cloneDate = curDump->cloneDate;
1876 vhptr->server = curDump->hostAddr;
1877 vhptr->part = curDump->partition;
1878 vhptr->from = curDump->date;
1879 vhptr->frag = fragmentNumber;
1881 vhptr->magic = TC_VOLBEGINMAGIC;
1882 vhptr->dumpID = dparamsPtr->databaseDumpId; /* real dump id */
1883 vhptr->level = nodePtr->level;
1884 vhptr->parentID = nodePtr->parent;
1886 vhptr->versionflags = CUR_TAPE_VERSION;
1887 strcpy(vhptr->dumpSetName, nodePtr->dumpSetName);
1888 strcpy(vhptr->preamble, "H++NAME#");
1889 strcpy(vhptr->postamble, "T--NAME#");
1895 volumeHeader_hton(struct volumeHeader *hostPtr, struct volumeHeader *netPtr)
1897 struct volumeHeader volHdr;
1899 strcpy(volHdr.preamble, hostPtr->preamble);
1900 strcpy(volHdr.postamble, hostPtr->postamble);
1901 strcpy(volHdr.volumeName, hostPtr->volumeName);
1902 strcpy(volHdr.dumpSetName, hostPtr->dumpSetName);
1903 volHdr.volumeID = htonl(hostPtr->volumeID);
1904 volHdr.server = htonl(hostPtr->server);
1905 volHdr.part = htonl(hostPtr->part);
1906 volHdr.from = htonl(hostPtr->from);
1907 volHdr.frag = htonl(hostPtr->frag);
1908 volHdr.magic = htonl(hostPtr->magic);
1909 volHdr.contd = htonl(hostPtr->contd);
1910 volHdr.dumpID = htonl(hostPtr->dumpID);
1911 volHdr.level = htonl(hostPtr->level);
1912 volHdr.parentID = htonl(hostPtr->parentID);
1913 volHdr.endTime = htonl(hostPtr->endTime);
1914 volHdr.versionflags = htonl(hostPtr->versionflags);
1915 volHdr.cloneDate = htonl(hostPtr->cloneDate);
1917 memcpy(netPtr, &volHdr, sizeof(struct volumeHeader));
1921 /* database related routines */
1924 createDump(struct dumpRock *dparamsPtr)
1926 struct dumpNode *nodePtr = dparamsPtr->node;
1927 struct budb_dumpEntry *dumpPtr;
1930 dumpPtr = &dparamsPtr->dump;
1931 memset(dumpPtr, 0, sizeof(*dumpPtr));
1933 /* id filled in by database */
1934 dumpPtr->parent = nodePtr->parent;
1935 dumpPtr->level = nodePtr->level;
1939 if (xbsaType == XBSA_SERVER_TYPE_ADSM) {
1940 strcpy(dumpPtr->tapes.tapeServer, butxInfo.serverName);
1941 dumpPtr->flags = BUDB_DUMP_ADSM;
1943 if (!(butxInfo.serverType & XBSA_SERVER_FLAG_MULTIPLE)) {
1944 /* The current server (API) doesn't provide the function required
1945 * to specify a server at startup time. For that reason, we can't
1946 * be sure that the server name supplied by the user in the user-
1947 * defined configuration file is correct. We set a flag here so
1948 * we know at restore time that the servername info in the backup
1949 * database may be incorrect. We will not allow a server switch
1950 * at that time, even if the server at restore time supports
1953 dumpPtr->flags |= BUDB_DUMP_XBSA_NSS;
1957 strcpy(dumpPtr->volumeSetName, nodePtr->volumeSetName);
1958 strcpy(dumpPtr->dumpPath, nodePtr->dumpName);
1959 strcpy(dumpPtr->name, nodePtr->dumpSetName);
1960 dumpPtr->created = 0; /* let database assign it */
1961 dumpPtr->incTime = 0; /* not really used */
1962 dumpPtr->nVolumes = 0;
1963 dumpPtr->initialDumpID = 0;
1965 dumpPtr->tapes.id = groupId;
1966 dumpPtr->tapes.b = 1;
1967 dumpPtr->tapes.maxTapes = 0;
1968 strcpy(dumpPtr->tapes.format, nodePtr->tapeSetDesc.format);
1970 /* principal filled in by database */
1972 /* now call the database to create the entry */
1973 code = bcdb_CreateDump(dumpPtr);
1975 dparamsPtr->databaseDumpId = dumpPtr->id;
1982 * Initialize to a specific server. The first time, we remember the
1983 * server as the original server and go back to it each time we pass 0
1987 InitToServer(afs_int32 taskId, struct butx_transactionInfo * butxInfoP,
1990 static char origserver[BSA_MAX_DESC];
1991 static int init = 0;
1992 afs_int32 rc, code = 0;
1995 strcpy(origserver, "");
2000 server = origserver; /* return to original server */
2001 if (strcmp(server, "") == 0)
2002 return 0; /* No server, do nothing */
2003 if (strcmp(butxInfoP->serverName, server) == 0)
2004 return 0; /* same server, do nothing */
2005 if (strcmp(origserver, "") == 0)
2006 strcpy(origserver, server); /* remember original server */
2008 if (strcmp(butxInfoP->serverName, "") != 0) {
2009 /* If already connected to a server, disconnect from it.
2010 * Check to see if our server does not support switching.
2012 if (!(butxInfo.serverType & XBSA_SERVER_FLAG_MULTIPLE)) {
2013 ErrorLog(0, taskId, TC_BADTASK, 0,
2014 "This version of XBSA libraries does not support switching "
2015 "from server %s to server %s\n", butxInfoP->serverName,
2017 return (TC_BADTASK);
2020 rc = xbsa_Finalize(&butxInfo);
2021 if (rc != XBSA_SUCCESS) {
2022 ErrorLog(0, taskId, rc, 0,
2023 "InitToServer: Unable to terminate the connection to server %s\n",
2024 butxInfoP->serverName);
2029 /* initialize to the new server */
2030 rc = xbsa_Initialize(&butxInfo, xbsaObjectOwner, appObjectOwner,
2031 xbsaSecToken, server);
2032 if (rc != XBSA_SUCCESS) {
2033 ErrorLog(0, taskId, rc, 0,
2034 "InitToServer: Unable to initialize the XBSA library to server %s\n",
2048 DeleteDump(void *param)
2050 struct deleteDumpIf *ptr = (struct deleteDumpIf *)param;
2053 afs_int32 rc, code = 0;
2055 afs_int32 index, next, dbTime;
2057 struct budb_dumpEntry dumpEntry;
2058 char tapeName[BU_MAXTAPELEN];
2059 char dumpIdStr[XBSA_MAX_OSNAME];
2060 char volumeNameStr[XBSA_MAX_PATHNAME];
2063 int allnotfound = 1, onenotfound = 0;
2064 extern struct udbHandleS udbHandle;
2065 extern struct deviceSyncNode *deviceLatch;
2067 setStatus(taskId, DRIVE_WAIT);
2068 EnterDeviceQueue(deviceLatch);
2069 clearStatus(taskId, DRIVE_WAIT);
2071 dumpid = ptr->dumpID;
2072 taskId = ptr->taskId; /* Get task Id */
2075 TapeLog(2, taskId, 0, 0, "Delete Dump %u\n", dumpid);
2077 vl.budb_volumeList_len = 0;
2078 vl.budb_volumeList_val = 0;
2081 /* Get the dump info for the dump we are deleting */
2082 rc = bcdb_FindDumpByID(dumpid, &dumpEntry);
2084 ErrorLog(0, taskId, rc, 0,
2085 "Unable to locate dump ID %u in database\n", dumpid);
2086 setStatus(taskId, TASK_ERROR);
2090 /* we must make sure that we are configured with the correct type of
2091 * XBSA server for this dump delete! Only those dumped to an ADSM server.
2093 if ((xbsaType == XBSA_SERVER_TYPE_ADSM)
2094 && !((dumpEntry.flags & (BUDB_DUMP_ADSM | BUDB_DUMP_BUTA)))) {
2095 ErrorLog(0, taskId, TC_BADTASK, 0,
2096 "The dump %u requested for deletion is incompatible with this instance of butc\n",
2098 setStatus(taskId, TASK_ERROR);
2099 ERROR_EXIT(TC_BADTASK);
2102 /* Make sure we are connected to the correct server. If not, switch to it if appropriate */
2103 if ((strlen((char *)dumpEntry.tapes.tapeServer) != 0)
2104 && (strcmp((char *)dumpEntry.tapes.tapeServer, butxInfo.serverName) !=
2107 /* Check to see if the tapeServer name is trustworthy */
2108 if ((dumpEntry.flags & (BUDB_DUMP_XBSA_NSS | BUDB_DUMP_BUTA))
2109 && !forcemultiple) {
2110 /* The dump was made with a version of the XBSA interface
2111 * that didn't allow switching of servers, we can't be sure
2112 * that the servername in the backup database is correct. So,
2113 * we will check the servername and log it if they don't match;
2114 * but we will try to do the delete without switching servers.
2117 "The dump %d requested for deletion is on server %s "
2118 "but butc is connected to server %s "
2119 "(Attempting to delete the dump anyway)\n", dumpid,
2120 (char *)dumpEntry.tapes.tapeServer, butxInfo.serverName);
2123 "The dump %u requested for deletion is on server %s "
2124 "but butc is connected to server %s "
2125 "(switching servers)\n", dumpid,
2126 (char *)dumpEntry.tapes.tapeServer, butxInfo.serverName);
2128 rc = InitToServer(taskId, &butxInfo,
2129 (char *)dumpEntry.tapes.tapeServer);
2130 if (rc != XBSA_SUCCESS) {
2131 setStatus(taskId, TASK_ERROR);
2137 /* Start a new Transaction */
2138 rc = xbsa_BeginTrans(&butxInfo);
2139 if (rc != XBSA_SUCCESS) {
2140 ErrorLog(0, taskId, rc, 0, "Unable to create a new transaction\n");
2141 setStatus(taskId, TASK_ERROR);
2146 /* Query the backup database for list of volumes to delete */
2147 for (index = next = 0; index != -1; index = next) {
2148 rc = ubik_Call_SingleServer(BUDB_GetVolumes, udbHandle.uh_client,
2149 UF_SINGLESERVER, BUDB_MAJORVERSION,
2150 BUDB_OP_DUMPID, tapeName, dumpid, 0,
2151 index, &next, &dbTime, &vl);
2153 if (rc == BUDB_ENDOFLIST)
2155 ErrorLog(0, taskId, rc, 0, "Can't find volume info for dump %d\n",
2157 setStatus(taskId, TASK_ERROR);
2161 /* Delete all volumes on the list */
2162 for (i = 0; i < vl.budb_volumeList_len; i++) {
2163 if (dumpEntry.flags & BUDB_DUMP_BUTA) {
2164 /* dump was from buta, use old buta style names */
2165 sprintf(dumpIdStr, "/%d", dumpid);
2166 strcpy(volumeNameStr, "/");
2167 strcat(volumeNameStr, (char *)vl.budb_volumeList_val[i].name);
2168 } else { /* BUDB_DUMP_ADSM */
2169 /* dump was from butc to ADSM, use butc names */
2170 strcpy(dumpIdStr, butcdumpIdStr);
2171 sprintf(volumeNameStr, "/%d", dumpid);
2172 strcat(volumeNameStr, "/");
2173 strcat(volumeNameStr, (char *)vl.budb_volumeList_val[i].name);
2176 rc = xbsa_DeleteObject(&butxInfo, dumpIdStr, volumeNameStr);
2177 if (rc != XBSA_SUCCESS) {
2178 ErrorLog(0, taskId, rc, 0,
2179 "Unable to delete the object %s of dump %s from the server\n",
2180 volumeNameStr, dumpIdStr);
2181 /* Don't exit if volume was not found */
2182 if (rc != BUTX_DELETENOVOL) {
2190 "Deleted volume %s (%u) in dumpID %u from the backup server\n",
2191 vl.budb_volumeList_val[i].name,
2192 vl.budb_volumeList_val[i].id, dumpid);
2196 /* free the memory allocated by RPC for this list */
2197 if (vl.budb_volumeList_val)
2198 free(vl.budb_volumeList_val);
2199 vl.budb_volumeList_len = 0;
2200 vl.budb_volumeList_val = 0;
2205 rc = xbsa_EndTrans(&butxInfo);
2206 if (rc != XBSA_SUCCESS) {
2207 ErrorLog(0, taskId, rc, 0,
2208 "Unable to terminate the current transaction\n");
2209 setStatus(taskId, TASK_ERROR);
2214 /* Switch back to the original server */
2215 rc = InitToServer(taskId, &butxInfo, NULL);
2217 if (vl.budb_volumeList_val)
2218 free(vl.budb_volumeList_val);
2220 setStatus(taskId, TASK_DONE);
2221 FreeNode(taskId); /* free the dump node */
2222 LeaveDeviceQueue(deviceLatch);
2224 /* If we don't find any dumps on the server, rather than returning
2225 * a success, return a failure.
2227 if (!code && onenotfound && allnotfound) {
2228 code = BUTX_DELETENOVOL;
2229 setStatus(taskId, TASK_ERROR);
2231 return (void *)(code);