/* IBM_PROLOG_BEGIN_TAG */ /* This is an automatically generated prolog. */ /* */ /* */ /* */ /* Licensed Materials - Property of IBM */ /* */ /* (C) COPYRIGHT International Business Machines Corp. 2004,2006 */ /* All Rights Reserved */ /* */ /* US Government Users Restricted Rights - Use, duplication or */ /* disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ /* */ /* IBM_PROLOG_END_TAG */ #pragma comment (copyright, "@(#)Licensed Materials-Property of IBM") static char sccsid[] = "@(#)46 1.5 src/avs/fs/mmfs/samples/util/tsbackup.C, mmfs, avs_rgpfs24, rgpfs24s001a 4/4/06 09:37:12"; /*=========================================================================== * * tsbackup: a utility for backing up a GPFS filesystem to a TSM server * * Syntax: * * tsbackup Device MountPoint -n ControlFile [-t {full | incremental}] * [-r IOrate] [-T] * or * * tsbackup Device MountPoint -R [-r IOrate] [-T] * *==========================================================================*/ #ifdef GPFS_LINUX /* Use 64 bit version of stat, etc. */ #define _LARGEFILE_SOURCE #define _FILE_OFFSET_BITS 64 typedef long long offset_t; #endif #ifdef GPFS_AIX /* Use 64 bit version of stat, etc. */ #ifndef _LARGE_FILES #define _LARGE_FILES #endif #endif #include #include #include #include #include #include #include // The following includes are for pipeOpen(), pipeClose(), etc. #include #include #include #include #include #include extern "C" { #include #include } #include #include "tsbackup.h" // all relevant structures for backup /* forward declarations */ extern "C" char *basename(const char *); /* globals for the tsbackup program */ char deviceName[MAX_FILE_NAME]; char fsName[MAX_FILE_NAME]; char inputCtrlFile[MAX_FILE_NAME]; const char* snapshotPathname; char snapshotSubdir[MAX_FILE_NAME]; char sstring[MAX_COMMAND_STRING]; pid_t msgChildPid = 0; gpfs_fssnap_id_t prevSnapId; static char *transactionCmdExpire = "expire"; static char *transactionCmdIncremental = "incremental"; static char *transactionCmdSelective = "selective"; static char *transactionCmdOption = "filelist"; static char *backupControlName = ".mmbuControl"; static char *backupShadowName = ".mmbuShadow"; static char *shadowCheckName = ".mmbuShadowCheck"; static char *backupFilesizesName = ".mmbuFilesizes"; static char *fsSnapshotName_base = ".mmbuSnapshot"; static char *transListName = ".mmbuTrans"; static char *pendingDeletionsName = ".mmbuPendingDels"; static char *pendingChangesName = ".mmbuPendingChgs"; static char *changesName = ".mmbuChanges"; static char *deletionsName = ".mmbuDeletions"; inodeBitsArray inodeBitsP = NULL; inodeBitsArray inodeBits2P = NULL; char *RootFsDirP; /* Define the number of threads used to read the directories. */ #define DEFAULT_THREADS 24 int nThreads = DEFAULT_THREADS; /* Define wait queue for threads. */ static pthread_mutex_t QueueMutex; static pthread_cond_t QueueCond; static int NWorkersWaiting = 0; static int NWorkersRunning = 0; /* Define mutex to serialize the output. */ static pthread_mutex_t OutputMutex; static QueueElement *WorkQueueP = NULL; /* * Define storage for variables declared in tsbackup.h */ Int32 ioRate = 100; /* I/O rate */ Int32 Tracing = 0; /* tracing control flag */ Int32 Full; /* backup type full (true or false) */ Int32 Incremental; /* backup type incremental (true or false) */ Int32 Resume; /* resume backup (true or false) */ char masterNode[MAX_NAME_CHARS]; /* node on which tsbackup was invoked */ Int32 masterPID; /* pid for master tsbackup process */ char fsBackupCtrl[MAX_FILE_NAME]; char fsSnapshotName[MAX_FILE_NAME]; char fsSnapshotPathname[MAX_FILE_NAME]; gpfs_fssnap_handle_t *fsSnapHandleP = NULL; char filesizesFile[MAX_FILE_NAME]; Int32 filesizesHandle; char backupShadowFile[MAX_FILE_NAME]; char backupShadowCheck[MAX_FILE_NAME]; char shadowFile[MAX_BACKUP_CLIENTS][MAX_FILE_NAME]; Int32 shadowFileHandle[MAX_BACKUP_CLIENTS]; Int64 shadowFileNumberOfRecords[MAX_BACKUP_CLIENTS]; char transactionsList[MAX_FILE_NAME]; Int32 transactionsListHandle; char clientTransactionsList[MAX_BACKUP_CLIENT_PROCESSES][MAX_FILE_NAME]; Int32 clientTransactionsListHandle[MAX_BACKUP_CLIENT_PROCESSES]; char pendingTransactionsList[MAX_FILE_NAME]; char pendingTransactionsListName[MAX_FILE_NAME]; char clientPendingTransactionsList[MAX_BACKUP_CLIENT_PROCESSES][MAX_FILE_NAME]; char changesFile[MAX_FILE_NAME]; Int32 fp_chg; char deletionsFile[MAX_FILE_NAME]; Int32 fp_del; Boolean fileDeletions; Boolean fileChanges; gpfs_backup_control_t *backupControlP; extern int errno; #ifndef true #define true 1 #endif #ifndef false #define false 0 #endif /* * print usage message and exit */ static void usage(char *argv0) { fprintf(stderr, "Usage:\n %s Device MountPoint -n ControlFile [-t {full | incremental}] [-r IOrate]\n or\n %s Device MountPoint -R [-r IOrate]\n", basename(argv0), basename(argv0)); exit(RC_FAIL); } #define TSSIZE 128 /* time buffer size */ /* * print trace line for entering a function */ void traceEntry(char* filen, char* funcn, Int32 linen) { time_t nowTime; char timebuf[TSSIZE]; struct tm CurrentTime; time(&nowTime); strftime(timebuf, TSSIZE, "%X" , localtime_r(&nowTime, &CurrentTime)); fprintf(stderr, "Trace: time %s, %s, line %d: enter %s()\n", timebuf, filen, linen, funcn); } /* * print trace line for exiting a function */ void traceExit(char* filen, char* funcn, Int32 linen) { time_t nowTime; char timebuf[TSSIZE]; struct tm CurrentTime; time(&nowTime); strftime(timebuf, TSSIZE, "%X" , localtime_r(&nowTime, &CurrentTime)); fprintf(stderr, "Trace: time %s, %s, line %d: exit %s()\n", timebuf, filen, linen, funcn); } /* * print trace line other than a function entry or exit */ void traceLine(char* filen, char* funcn, Int32 linen) { time_t nowTime; char timebuf[TSSIZE]; struct tm CurrentTime; time(&nowTime); strftime(timebuf, TSSIZE, "%X" , localtime_r(&nowTime, &CurrentTime)); fprintf(stderr, "Trace: time %s, %s, line %d, %s()\n", timebuf, filen, linen, funcn); } #if 0 #ifndef TIMELEN #define TIMELEN 26 #endif /* Return ptr to buffer passed in with current time filled in the buffer (year removed) */ char * consoleTime(char timeBufP[TIMELEN]) { time_t TimeNow; char *p; time(&TimeNow); ctime_r(&TimeNow, timeBufP); if ((p = strchr(timeBufP, '\n')) != NULL) *p = '\0'; return timeBufP; } #endif /* signal handler for SIGINT */ void sig_interrupt(int signo) { Int32 j, tmp; struct stat statBuf; /* Delete any temporary files still lying around. */ /* Delete any existing client transactions files. */ for (j = 0; j < MAX_BACKUP_CLIENT_PROCESSES; j++) { unlink(clientTransactionsList[j]); unlink(clientPendingTransactionsList[j]); } /* Delete the original transactions file now that we are done with it. */ unlink(transactionsList); unlink(changesFile); unlink(filesizesFile); unlink(deletionsFile); /* Delete the snapshot if it exists. */ if (stat(fsSnapshotPathname, &statBuf) != -1) { /* Delete the snapshot. */ sprintf(sstring, "/usr/lpp/mmfs/bin/tsdelsnapshot %s %s > /dev/null", deviceName, fsSnapshotName); tmp = system(sstring); /* Check system() return. */ if (tmp != 0) { fprintf(stderr, "system(%s) returned rc = %d\n", sstring, tmp); } } /* If there is a backup control data structure, set the completion level to none and write the updated backup control data to the control file. */ if (backupControlP != 0) { backupControlP->backupHdr.completionLevel = BACKUP_COMPLETION_NONE; tmp = createBackupCtrlFile(fsBackupCtrl, backupControlP); } exit(RC_FAIL); } /*------* * main * *------*/ int main(int argc, char *argv[]) { char linein[LINE_LENGTH]; /* buffer used in reading a line via fget */ Int32 tmp, rc = RC_SUCCESS; Int32 length; Int32 i, j, k; int c; struct stat statBuf; #if 0 char timeBufP[TIMELEN]; #endif char timeString[MAX_FILE_NAME]; FILE* filePtr = NULL; /* file pointer for temp files and usage in popen() */ static char *devPrefix = "/dev/"; static char *mpPrefix = "/"; gpfs_direntx_t *shadow_controlP; gpfs_backup_control_t *backupControlLocalP = NULL; backup_control_hdr_t *backupControlHdrP = NULL; char* cp; char* backupType; char* fn = "main"; /* Set signal handler for SIGINT */ if (signal(SIGINT, sig_interrupt) == SIG_ERR) { printf("tsbackup: Unable to set signal handler for SIGINT.\n"); } /* At least two parms must be passed, since the device and the mountpoint parms are required. */ if (argc < 3) { fprintf(stderr, "An incorrect number of parameters was passed.\n"); usage(argv[0]); } /* Set the backup type flags prior to parsing the command parms. */ Incremental = false; Full = false; Resume = false; /* Parse the command parameters. */ /* The first arg is the device parameter, and it is mandatory. */ strcpy(deviceName, argv[1]); if ((strncmp(deviceName, devPrefix, strlen(devPrefix)) != 0) || (strcmp(deviceName, devPrefix) == 0)) { fprintf(stderr, "An incorrect parameter (%s) was specified for the device.\nThe value must begin with /dev/.\n", deviceName); usage(argv[0]); } /* The second arg is the mountpoint parameter, and it is also mandatory. */ strcpy(fsName, argv[2]); if ((strncmp(fsName, mpPrefix, strlen(mpPrefix)) != 0) || (strcmp(fsName, mpPrefix) == 0)) { fprintf(stderr, "An incorrect parameter (%s) was specified for the mountpoint.\nThe value must begin with a /.\n", fsName); usage(argv[0]); } /* Clear the input control file string before processing parameters. */ *inputCtrlFile = '\0'; /* Adjust argc and argv now that we've processed the first two parameters. */ argc = argc - 2; argv[2] = argv[0]; argv = &argv[2]; /* Parse the remainder of the parameter string. */ while ((c = getopt(argc, argv, "n:r:Rt:T")) != EOF) { switch (c) { case 'n': // control file if (Resume) { fprintf(stderr, "Invalid combination: -n and -R\n"); usage(argv[0]); } strncpy(inputCtrlFile, optarg, MAX_FILE_NAME); if (stat(inputCtrlFile, &statBuf) == -1) // Does the file exist? { fprintf(stderr, "Control file %s does not exist, errno=%d\n", inputCtrlFile, errno); exit(RC_FAIL); } break; case 'r': // I/O rate ioRate = atoi((const char *) (optarg)); if ((ioRate <= 0) || (ioRate > 100)) { fprintf(stderr, "An incorrect value (%d) was specified for IOrate.\nSpecify a value between 0 and 100.\n", ioRate); usage(argv[0]); } break; case 'R': // resume backup if (strcmp((const char *) inputCtrlFile, "") != 0) { fprintf(stderr, "Invalid combination: -n and -R\n"); usage(argv[0]); } if (Full || Incremental) { fprintf(stderr, "Invalid combination: -t and -R\n"); usage(argv[0]); } Resume = true; Incremental = false; Full = false; break; case 't': // backup type (full or incremental) if (Resume) { fprintf(stderr, "Invalid combination: -t and -R\n"); usage(argv[0]); } if ( (strcmp((const char *) optarg, "full") == 0) || (strcmp((const char *) optarg, "Full") == 0) ) { Full = true; Incremental = false; Resume = false; } else if ( (strcmp((const char *) optarg, "incremental") == 0) || (strcmp((const char *) optarg, "Incremental") == 0) ) { Incremental = true; Full = false; Resume = false; } else { fprintf(stderr, "The value (%s) specified for the -t flag is not valid.\n", optarg); usage(argv[0]); } break; case 'T': // enable tracing Tracing = true; break; default: usage(argv[0]); } } if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Initializations */ for (i = 0; i < MAX_BACKUP_CLIENTS; i++) { shadowFileNumberOfRecords[i] = 0; } /* Obtain the pid number and node name for this invocation of tsbackup for later use by the mmexectsmcmd script. */ rc = gethostname(masterNode, MAX_NAME_CHARS); if (rc != 0) { fprintf(stderr, "gethostname() failed; unable to obtain the name of the invoking node.\n"); exit(RC_FAIL); } masterPID = getpid(); /* The names of the following files are based on the mount point of the filesystem. */ /* Construct the name for the backup control file. */ snprintf(fsBackupCtrl, MAX_FILE_NAME, "%s/%s", fsName, backupControlName); /* Construct the name for the filesizes file. */ snprintf(filesizesFile, MAX_FILE_NAME, "%s/%s", fsName, backupFilesizesName); /* Construct the name for the backup shadow file. */ snprintf(backupShadowFile, MAX_FILE_NAME, "%s/%s", fsName, backupShadowName); /* Construct the name for the prior backup shadow file. */ snprintf(backupShadowCheck, MAX_FILE_NAME, "%s/%s", fsName, shadowCheckName); /* Create the name of the transactionsList file. */ snprintf(transactionsList, MAX_FILE_NAME, "%s/%s", fsName, transListName); #if 0 // BCH - The following code snippet was left here to demonstrate how to // BCH use the consoleTime routine should it be needed in the future. /* Construct a timestamp-based name for the snapshot to ensure its uniqueness (and thus avoid name collisions). */ snprintf(timeString, MAX_FILE_NAME, "%s", consoleTime(timeBufP)); while ((cp = strchr(timeString, ' ')) != NULL) /* replace blanks with */ { /* underscore chars */ *cp = '_'; } snprintf(fsSnapshotName, MAX_FILE_NAME, "%s_%s", fsSnapshotName_base, timeString); #endif /* Construct the name for the snapshot of the filesystem. */ snprintf(fsSnapshotName, MAX_FILE_NAME, "%s", fsSnapshotName_base); /* Process whatever type of backup was specified. */ if (Full) /* full backup */ { assert((Resume == false) && (Incremental == 0)); rc = doFSFullBackup(fsName); switch (rc) { case RC_FAIL: /* failure */ fprintf(stderr, "doFSFullBackup() returned rc = %d\n", rc); fprintf(stderr, "tsbackup: full backup failed, rc = %d\n", rc); break; case RC_PSUCCESS: /* partial success */ fprintf(stderr, "tsbackup: full backup finished with partial success, rc = %d\n", rc); break; case RC_SUCCESS: /* complete success */ printf( "tsbackup: full backup finished with complete success, rc = %d\n", rc); break; default: /* unexpected rc */ fprintf(stderr, "doFSFullBackup() returned rc = %d\n", rc); rc = RC_FAIL; fprintf(stderr, "tsbackup: full backup failed, rc = %d\n", rc); break; } } /* end full backup */ else if (Incremental == 1) /* incremental backup */ { /* If an input control file was passed it will be ignored and the information in the backup control file will be used instead. */ rc = doFSIncrementalBackup(fsName); switch (rc) { case RC_FAIL: /* failure */ fprintf(stderr, "doFSIncrementalBackup() returned rc = %d\n", rc); fprintf(stderr, "tsbackup: incremental backup failed, rc = %d\n", rc); return(rc); case RC_PSUCCESS: /* partial success */ fprintf(stderr, "tsbackup: incremental backup finished with partial success, rc = %d\n", rc); break; case RC_SUCCESS: /* complete success */ printf( "tsbackup: incremental backup finished with complete success, rc = %d\n", rc); break; default: /* unexpected rc */ fprintf(stderr, "doFSIncrementalBackup() returned unexpected rc = %d\n", rc); rc = RC_FAIL; fprintf(stderr, "tsbackup: incremental backup failed, rc = %d\n", rc); } } /* end incremental backup */ else if (Resume) /* resume backup */ { /* * A resume backup may be done if the previous backup operation * finished with partial success. This information is saved * in the backup control file. Additionally, with partial success * a file named .PendingTransactions should exist which contains * the names of the files which were not successfully backed up * during the previous backup attempt. * If an input control file was passed, it will be ignored and the * existing information in the backup control file will be used instead. */ rc = doFSResumeBackup(fsName, &backupType); switch (rc) { case RC_FAIL: /* failure */ fprintf(stderr, "tsbackup: resume of %s backup failed, rc = %d\n", backupType, rc); break; case RC_PSUCCESS: /* partial success */ fprintf(stderr, "tsbackup: resume of %s backup finished with partial success, rc = %d\n", backupType, rc); break; case RC_SUCCESS: /* complete success */ printf( "tsbackup: resume of %s backup finished with complete success, rc = %d\n", backupType, rc); break; default: /* unexpected rc */ fprintf(stderr, "doFSResumeBackup() returned unexpected rc = %d\n", rc); rc = RC_FAIL; fprintf(stderr, "tsbackup: resume of %s backup failed, rc = %d\n", backupType, rc); break; } } /* end resume backup */ else { /* We should not get here. */ fprintf(stderr, "Invalid parms: specify full, incremental, or resume.\n"); rc = RC_FAIL; errno = EINVAL; usage(argv[0]); } /* end if (Full) */ /* If the the message process was forked, kill it now, since tsbackup is about to exit. */ if (msgChildPid != 0) { tmp = kill(msgChildPid, SIGTERM); } if (Tracing) traceExit("tsbackup.C", fn, __LINE__); exit(rc); } /*------ end of main ----------------*/ /* * NAME: doFSFullBackup() * * FUNCTION: * Do a full backup of a filesystem. * * PARAMETERS: * fsName: (IN) The full path name of the mounted filesystem. * * RETURNS: * RC_SUCCESS on success, RC_PSUCCESS on partial success, RC_FAIL on error. * * NOTES: * - Take a new snapshot under the new name. * - Backup the new snapshot. * - For each server involved with the previous backup, have a client issue * the "Delete Filespace" TSM command. */ int doFSFullBackup(char *fsName) { Int32 tmp, keep_rc, rc = RC_SUCCESS; backup_control_hdr_t *backupControlHdrP = NULL; struct stat statBuf; char* fn = "doFSFullBackup"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Check whether there is a backup control file from a previous backup. */ rc = checkBackupCtrlFile(); if (rc == RC_SUCCESS) { /* Load the global backup control structure with the information from the backup control file. */ rc = processBackupCtrlFile(fsName, fsBackupCtrl, &backupControlP); if (rc != RC_SUCCESS) { fprintf(stderr, "processBackupCtrlFile() returned rc = %d\n", rc); if (backupControlP != NULL) free((void *) backupControlP); return(RC_FAIL); } /* If an input control file was specified, load the information in it into the backup control structure just initialized by the call to processBackupCtrlFile(). This will cause the contents of the input control file to override the controls in effect at the time of the last backup. */ if (inputCtrlFile != NULL) { rc = processInputCtrlFile(fsName, inputCtrlFile, &backupControlP, false); if (rc != RC_SUCCESS) { fprintf(stderr, "processInputCtrlFile() returned with rc = %d\n", rc); if (backupControlP != NULL) free((void *) backupControlP); return(RC_FAIL); } } } else { /* Here if there is no backup control file (i.e., this is the first backup). The user should have specified an input control file. */ /* If an input control file was specified, use its information to create the backup control structure. */ if (inputCtrlFile != NULL) { rc = processInputCtrlFile(fsName, inputCtrlFile, &backupControlP, true); if (rc != RC_SUCCESS) { fprintf(stderr, "processInputCtrlFile() returned with rc = %d\n", rc); if (backupControlP != NULL) free((void *) backupControlP); return(RC_FAIL); } } else { /* There is no input control file and no prior backup control file. Print an error message and return failure. */ fprintf(stderr, "Unable to proceed because no control file was specified and there is no prior backup control file.\n"); return(RC_FAIL); } } backupControlHdrP = (backup_control_hdr_t *) backupControlP; /* Delete any pending files left over from a prior backup that was partially successful. Once a full backup is issued, a resume backup is no longer an option. */ snprintf(sstring, MAX_FILE_NAME, "%s/%s", fsName, pendingDeletionsName); unlink(sstring); snprintf(sstring, MAX_FILE_NAME, "%s/%s", fsName, pendingChangesName); unlink(sstring); /* Create a child process to issue reassuring messages to the user in case the remaining work takes a long time. */ forkMsgChild(); /* Create a snapshot and store it at the mount point of the filesystem. */ rc = createBackupSnapshot(deviceName, fsSnapshotName); if (rc != RC_SUCCESS) { fprintf(stderr, "createBackupSnapshot returned with rc = %d.\n", rc); free((void *) backupControlP); return(RC_FAIL); } else { /* Save the name of the snapshot in the gpfs_backup_control_t structure. */ strncpy(backupControlHdrP->snapshotName, fsSnapshotName, MAX_FILE_NAME); } /* Do a backup of the snapshot and determine the level of success. */ rc = doFSBackup(fsName, fsSnapshotPathname); switch (rc) { case RC_SUCCESS: /* The operation succeeded. */ keep_rc = rc; backupControlP->backupHdr.completionLevel = BACKUP_COMPLETION_FULL; break; case RC_PSUCCESS: /* The operation partially succeeded. */ keep_rc = rc; backupControlP->backupHdr.completionLevel = BACKUP_COMPLETION_PARTIAL; strcpy(backupControlHdrP->backupType, "full"); break; case RC_FAIL: /* The operation failed. */ default: /* Indicate an unexpected error. */ if (rc != RC_FAIL) { fprintf(stderr, "doFSBackup() returned unexpected rc = %d\n", rc); } else { fprintf(stderr, "doFSBackup() returned rc = %d\n", rc); } keep_rc = RC_FAIL; backupControlP->backupHdr.completionLevel = BACKUP_COMPLETION_NONE; break; } /* Write the updated backup control data to the backup control file. */ rc = createBackupCtrlFile(fsBackupCtrl, backupControlP); if (rc != RC_SUCCESS) { /* Indicate error to user. */ free((void *) backupControlP); return(RC_FAIL); } /* Delete the snapshot if the completion level was anything other than partial (we no longer need the snapshot if the completion level was success or failure). For completion level partial, the snapshot is not deleted so that it is available for a subsequent resume backup. */ if (backupControlP->backupHdr.completionLevel != BACKUP_COMPLETION_PARTIAL) { /* Delete the snapshot. */ sprintf(sstring, "/usr/lpp/mmfs/bin/tsdelsnapshot %s %s > /dev/null", deviceName, fsSnapshotName); tmp = system(sstring); /* Check system() return. */ if (tmp != 0) { fprintf(stderr, "system(%s) returned rc = %d\n", sstring, tmp); free((void *) backupControlP); return(RC_FAIL); } } /* Release the memory for the backup control structure. */ free((void *) backupControlP); if (Tracing) traceExit("tsbackup.C", fn, __LINE__); /* Return to caller with appropriate return code. */ return(keep_rc); } /*------ end of doFSFullBackup() ----------------*/ /* * NAME: doFSIncrementalBackup() * * FUNCTION: * Do an incremental backup of a filesystem. * * PARAMETERS: * fsName: (IN) The full path name of the mounted filesystem. * * RETURNS: * RC_SUCCESS on success, RC_PSUCCESS on partial success, RC_FAIL on error. * * NOTES: * OPERATION: BCH - these comments no longer match the code well * - Take a new snapshot and store it at a directory with the previous name. * - Handle the deleted files from the FS. (backupControlP is used) * - Perform an inodescan operation to determine all directories and * files of the FS. * - Perform a directory traversal and construct the .backup_shadow file. * - Compare the .backup_shadow and .backup_shadow_check files to * determine the list of files which need to be deleted. * Create a file named .backup_deletions which will contain all * these file names. * - Handle the files of the filesystem which have changed or are new. * - Perform an inodescan operation to determine all directories * and files of the filesystem which are new or have changed, * passing the old snapshot_ID. * - Perform a directory traversal and construct the .backup_shadow file. * - Extract all the file names from this .backup_shadow file and put * them into a .backup_changes file. */ int doFSIncrementalBackup(char *fsName) { Int32 tmp, rc, rc1 = RC_SUCCESS, rc2 = RC_SUCCESS; gpfs_backup_control_t *backupControlLocalP = NULL; backup_control_hdr_t *backupControlHdrP = NULL; struct stat statBuf; char* fn = "doFSIncrementalBackup"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Check whether there is a backup control file. */ rc = checkBackupCtrlFile(); if (rc != RC_SUCCESS) { /* Error: there is no backup control file. */ /* Print error message and return failure. */ fprintf(stderr, "A backup type of incremental was specified, but a backup control file\nfrom a previous backup does not exist.\n"); return(RC_FAIL); } /* Load the local backup control structure with the information from the latest backup control file. */ rc = processBackupCtrlFile(fsName, fsBackupCtrl, &backupControlLocalP); if (rc != RC_SUCCESS) { fprintf(stderr, "processBackupCtrlFile() returned rc = %d\n", rc); if (backupControlP != NULL) free((void *) backupControlP); return(RC_FAIL); } /* If the previous backup did not complete successfully, issue a message that it must be completed before an incremental backup will proceed. */ if (backupControlLocalP->backupHdr.completionLevel != BACKUP_COMPLETION_FULL) { fprintf(stderr, "An incremental backup cannot be performed because the most recent backup did not complete successfully.\n"); // BCH - msg catalog cand. free((void *) backupControlLocalP); return(RC_FAIL); } backupControlHdrP = (backup_control_hdr_t *) backupControlLocalP; /* If an input control file was specified, load the information in it into the backup control structure just initialized by the call to processBackupCtrlFile(). This will cause the contents of the input control file to override the controls in effect at the time of the last backup. */ if (inputCtrlFile != NULL) { rc = processInputCtrlFile(fsName, inputCtrlFile, &backupControlLocalP, false); if (rc != RC_SUCCESS) { fprintf(stderr, "processInputCtrlFile() returned with rc = %d\n", rc); return(RC_FAIL); } } /* Set the global variable backupControlP to point to the backupControlLocalP structure for the soon-to-happen call to doFSFileDeletions(). */ backupControlP = backupControlLocalP; /* Create a child process to issue reassuring messages to the user in case the remaining work takes a long time. */ forkMsgChild(); /* Create a snapshot and store it at the mount point of the filesystem. */ rc = createBackupSnapshot(deviceName, fsSnapshotName); if (rc != RC_SUCCESS) { fprintf(stderr, "createBackupSnapshot returned with rc = %d.\n", rc); rc = RC_FAIL; goto release_storage; } else { /* Save the name of the snapshot in the gpfs_backup_control_t structure. */ strncpy(backupControlHdrP->snapshotName, fsSnapshotName, MAX_FILE_NAME); } /* Save the snapshot id from the previous backup for later use by doFSFileChanges. The value is saved here because it will be changed by doFSFileDeletions when it calls doInodeScan(). */ prevSnapId = backupControlP->backupHdr.snapshotId; /* First, process the files that have been deleted from the filesystem since the previous backup. */ rc1 = doFSFileDeletions(fsName, fsSnapshotPathname); switch (rc1) { case RC_PSUCCESS: /* The operation had partial success up to now. */ case RC_SUCCESS: /* The operation had a total success. */ break; case RC_FAIL: /* The operation failed. */ default: /* An unexpected error occurred. */ /* Indicate error. */ if (rc1 != RC_FAIL) { fprintf(stderr, "doFSFileDeletions() returned unexpected rc = %d\n",rc); } else { fprintf(stderr, "doFSFileDeletions() returned rc = %d\n", rc); } } /* end switch */ /* Next, update the archive to reflect the new and changed files in the filesystem. */ rc2 = doFSFileChanges(fsName, fsSnapshotPathname); switch (rc2) { case RC_PSUCCESS: /* The operation had partial success up to now. */ case RC_SUCCESS: /* The operation had a total success. */ break; case RC_FAIL: /* The operation failed. */ default: /* An unexpected error occurred. */ /* Indicate error. */ if (rc2 != RC_FAIL) { fprintf(stderr, "doFSFileChanges() returned unexpected rc = %d\n", rc); } else { fprintf(stderr, "doFSFileChanges() returned rc = %d\n", rc); } } /* end switch */ // Create rc as a composite of rc1 and rc2. if (rc1 == RC_FAIL || rc2 == RC_FAIL) { rc = RC_FAIL; } else if (rc1 == RC_PSUCCESS || rc2 == RC_PSUCCESS) { rc = RC_PSUCCESS; } else { rc = RC_SUCCESS; } /* Process the deletions and changes composite return code. */ switch (rc) { case RC_SUCCESS: /* The operation had total success. */ /* Delete any pending transactions file left over from a previous partial success. */ unlink(pendingTransactionsList); /* Erase the backup control file. */ unlink(fsBackupCtrl); /* Indicate complete success. */ backupControlP->backupHdr.completionLevel = BACKUP_COMPLETION_FULL; break; case RC_PSUCCESS: /* The operation had partial success. */ /* Erase the backup control file. */ unlink(fsBackupCtrl); /* Indicate partial success and store the backup type. */ backupControlP->backupHdr.completionLevel = BACKUP_COMPLETION_PARTIAL; strcpy(backupControlHdrP->backupType, "incremental"); break; case RC_FAIL: /* The operation failed. */ default: backupControlP->backupHdr.completionLevel = BACKUP_COMPLETION_NONE; break; } delete_snapshot: /* Delete the snapshot if the completion level was anything other than partial (we no longer need the snapshot if the completion level was success or failure). For completion level partial, the snapshot is not deleted so that it is available for a subsequent resume backup. */ if (backupControlP->backupHdr.completionLevel != BACKUP_COMPLETION_PARTIAL) { /* Delete the snapshot. */ sprintf(sstring, "/usr/lpp/mmfs/bin/tsdelsnapshot %s %s > /dev/null", deviceName, fsSnapshotName); tmp = system(sstring); /* Check system() return. */ if (tmp != 0) { fprintf(stderr, "system(%s) returned rc = %d\n", sstring, tmp); return(RC_FAIL); } } /* Write the updated backup control data to the backup control file. */ tmp = createBackupCtrlFile(fsBackupCtrl, backupControlP); if (tmp != RC_SUCCESS) { /* Indicate error to user. */ free((void *) backupControlP); return(RC_FAIL); } release_storage: free((void *) backupControlP); if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of doFSIncrementalBackup() ----------------*/ /* * NAME: doFSResumeBackup() * * FUNCTION: * Resume the latest backup operation of a filesystem. * * PARAMETERS: * fsName: (IN) the full path name of the mounted filesystem * backupType: (OUT) pointer to the type of the backup that was resumed * * RETURNS: * RC_SUCCESS on success, RC_PSUCCESS on partial success, RC_FAIL on error. * * NOTES: */ int doFSResumeBackup(char *fsName, char **backupType) { Int32 tmp, rc, rc1 = RC_SUCCESS, rc2 = RC_SUCCESS; gpfs_backup_control_t *backupControlP = NULL; Boolean pendingWorkFound = false; struct stat statBuf; char* fn = "doFSResumeBackup"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Check whether there is a backup control file. */ rc = checkBackupCtrlFile(); if (rc != RC_SUCCESS) { /* Error: there is no backup control file. Print error message and return failure. */ fprintf(stderr, "The resume flag was specified, but a backup control file from a\nprevious backup does not exist.\n"); return(RC_FAIL); } /* Load the global backup control structure with the information from the latest backup control file. */ rc = processBackupCtrlFile(fsName, fsBackupCtrl, &backupControlP); if (rc != RC_SUCCESS) { *backupType = "unknown type"; fprintf(stderr, "processBackupCtrlFile() returned rc = %d\n", rc); return(RC_FAIL); } /* Set the pointer to the backup type for use by the caller. */ *backupType = backupControlP->backupHdr.backupType; /* Verify that the current completion indication is partial completion. */ if (backupControlP->backupHdr.completionLevel != BACKUP_COMPLETION_PARTIAL) { fprintf(stderr, "Unable to resume; either the previous backup was completely successful,\nor it did not succeed enough to be resumable.\n"); // BCH - possible candidate for message catalog free(backupControlP); return(RC_FAIL); } /* Create the full path name for the filesystem snapshot. */ strncpy(fsSnapshotName, backupControlP->backupHdr.snapshotName, MAX_FILE_NAME); /* Get the snapshot handle for the snapshot, and then use the handle to get the full pathname of the snapshot. */ fsSnapHandleP = gpfs_get_fssnaphandle_by_name(deviceName, fsSnapshotName); if (fsSnapHandleP == NULL) { rc = errno; fprintf(stderr, "%s: gpfs_get_fssnaphandle_by_name(%s, %s): %s\n", "doFSResumeBackup", fsName, fsSnapshotName, strerror(rc)); return(RC_FAIL); } snapshotPathname = gpfs_get_pathname_from_fssnaphandle(fsSnapHandleP); if (snapshotPathname == NULL) { rc = errno; fprintf(stderr, "%s: gpfs_get_pathname_from_fssnaphandle(%s): %s\n", "doFSResumeBackup", fsSnapHandleP, strerror(rc)); return(RC_FAIL); } strcpy(fsSnapshotPathname, snapshotPathname); /* Create a child process to issue reassuring messages to the user in case the remaining work takes a long time. */ forkMsgChild(); /* Create the full pathname for the pending deletions file. */ snprintf(pendingTransactionsList, MAX_FILE_NAME, "%s/%s", fsName, pendingDeletionsName); /* Check whether the pending deletions file exists. */ tmp = stat(pendingTransactionsList, &statBuf); if (tmp == -1) { if (errno != ENOENT) { fprintf(stderr, "Could not stat file '%s', errno=%d\n", pendingTransactionsList, errno); return(RC_FAIL); } } else { /* Rename the pending deletions file to be the transactions file. */ tmp = rename(pendingTransactionsList, transactionsList); if (tmp != 0) { fprintf(stderr, "rename(%s, %s) returned %d\n", pendingTransactionsList, transactionsList, rc); free(backupControlP); return(RC_FAIL); } /* Invoke routine to process the transactions file. The true flag that is passed tells the routine to divy the transactions file among the client processes based on aggregate file size, not by number of files, which is what makes sense when doing backups. */ rc1 = processFilelist(fsName, transactionCmdExpire, transactionCmdOption, backupControlP, true); // Set flag to indicate something was found and processed. pendingWorkFound = true; } /* Create the full pathname for the pending changes file. */ snprintf(pendingTransactionsList, MAX_FILE_NAME, "%s/%s", fsName, pendingChangesName); /* Check whether the pending changes file exists. */ tmp = stat(pendingTransactionsList, &statBuf); if (tmp == -1) { if (errno != ENOENT) { fprintf(stderr, "Could not stat file '%s', errno=%d\n", pendingTransactionsList, errno); return(RC_FAIL); } } else { /* Rename the pending deletions file to be the transactions file. */ tmp = rename(pendingTransactionsList, transactionsList); if (tmp != 0) { fprintf(stderr, "rename(%s, %s) returned %d\n", pendingTransactionsList, transactionsList, rc); free(backupControlP); return(RC_FAIL); } /* Invoke routine to process the transactions file. The true flag that is passed tells the routine to divy the transactions file among the client processes based on aggregate file size, not by number of files, which is what makes sense when doing backups. */ rc2 = processFilelist(fsName, transactionCmdSelective, transactionCmdOption, backupControlP, true); // Set flag to indicate something was found and processed. pendingWorkFound = true; } // If no work was found, issue an error and exit. if (pendingWorkFound == false) { fprintf(stderr, "Resume was specified, but no pending work files were found.\n"); free(backupControlP); return(RC_FAIL); } // Create rc as a composite of rc1 and rc2. if (rc1 == RC_FAIL || rc2 == RC_FAIL) { rc = RC_FAIL; } else if (rc1 == RC_PSUCCESS || rc2 == RC_PSUCCESS) { rc = RC_PSUCCESS; } else { rc = RC_SUCCESS; } /* Process the deletions and changes composite return code. */ switch (rc) { case RC_SUCCESS: /* complete success */ /* Set the backup completion indicator to indicate full. */ backupControlP->backupHdr.completionLevel = BACKUP_COMPLETION_FULL; break; case RC_PSUCCESS: /* partial success */ /* Set the backup completion indicator to indicate partial success. */ backupControlP->backupHdr.completionLevel = BACKUP_COMPLETION_PARTIAL; break; case RC_FAIL: /* failure */ default: /* Unexpected rc */ /* Issue error message. */ fprintf(stderr, "processFilelist() returned rc = %d\n", rc); /* Set the backup completion indicator to indicate complete failure. */ backupControlP->backupHdr.completionLevel = BACKUP_COMPLETION_NONE; rc = RC_FAIL; break; } /* Recreate the backup control file with the updated control data. */ tmp = createBackupCtrlFile(fsBackupCtrl, backupControlP); if (tmp != RC_SUCCESS) { /* Indicate error to user. */ fprintf(stderr, "doFSResumeBackup(): createBackupCtrlFile() returned rc = %d\n", tmp); rc = RC_FAIL; } /* Delete the snapshot if the completion level was anything other than partial (we no longer need the snapshot if the completion level was success or failure). For completion level partial, the snapshot is not deleted so that it is available for a subsequent resume backup. */ if (backupControlP->backupHdr.completionLevel != BACKUP_COMPLETION_PARTIAL) { /* Delete the snapshot. */ sprintf(sstring, "/usr/lpp/mmfs/bin/tsdelsnapshot %s %s > /dev/null", deviceName, fsSnapshotName); tmp = system(sstring); /* Check system() return. */ if (tmp != 0) { fprintf(stderr, "system(%s) returned rc = %d\n", sstring, tmp); return(RC_FAIL); } } free(backupControlP); if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of doFSResumeBackup() ----------------*/ /* * NAME: doFSBackup() * * FUNCTION: * Do a full backup of a filesystem. * * PARAMETERS: * fsName: (IN) the full path name of the mounted filesystem * fsSnapshotPathName: (IN) the full path name of the filesystem * snapshot which is to be backed up * * RETURNS: * RC_SUCCESS on success, RC_PSUCCESS on partial success, RC_FAIL on error. * * NOTES: */ int doFSBackup(char *fsName, char *fsSnapshotPathname) { Int32 tmp, rc, rc1 = RC_SUCCESS, rc2 = RC_SUCCESS; Int32 numShadows; Int32 clientIndex, numClients; char filenameTemp[MAX_FILE_NAME]; inodeBitsArray *inodeBitsArrayP; struct stat statBuf; char* fn = "doFSBackup"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* If there is a prior backup (as evidenced by the existence of a shadow check file), process any files that have been deleted since that backup was made. */ if (stat(backupShadowCheck, &statBuf) != -1) { /* Process the files that have been deleted from the filesystem since the previous backup. */ rc1 = doFSFileDeletions(fsName, fsSnapshotPathname); switch (rc1) { case RC_PSUCCESS: /* The operation had partial success up to now. */ case RC_SUCCESS: /* The operation had a total success. */ break; case RC_FAIL: /* The operation failed. */ default: /* An unexpected error occurred. */ /* Indicate error. */ if (rc1 != RC_FAIL) { fprintf(stderr, "doFSFileDeletions() returned unexpected rc = %d\n", rc1); } else { fprintf(stderr, "doFSFileDeletions() returned rc = %d\n", rc1); } } /* end switch */ } else { /* We come here if there was no prior backup, so doFSFileDeletions was not called. As a result, the backup shadow file has not been created yet. Proceed to do that now. */ /* Do an inodescan. */ inodeBitsArrayP = &inodeBitsP; rc2 = doInodeScan(fsSnapshotPathname, inodeBitsArrayP, backupControlP); if (rc2 != RC_SUCCESS) { fprintf(stderr, "doFSBackup(): doInodeScan() returned rc = %d\n", rc2); return(RC_FAIL); } /* Create a backup shadow file, and store it at the mount point of the filesystem. */ numShadows = 1; rc2 = createSnapshotShadows(fsName, numShadows, inodeBitsP); if (rc2 != RC_SUCCESS) { fprintf(stderr, "createSnapshotShadows() returned rc = %d\n", rc2); free(inodeBitsP); return(RC_FAIL); } free(inodeBitsP); /* Sort the backup shadow file(s) by inode number. */ rc2 = sortShadowfilesByInode(numShadows); if (rc2 != RC_SUCCESS) { fprintf(stderr, "sortShadowfilesByInode() returned rc = %d\n", rc2); return(RC_FAIL); } /* Update the .backup_shadow0 file with the correct file sizes and erase the filesizes file. */ rc2 = updateShadowfilesFilesizes(numShadows); if (rc2 != RC_SUCCESS) { fprintf(stderr, "updateShadowfilesFilesizes() returned rc = %d\n", rc2); return(RC_FAIL); } /* Sort the backup shadow file into alphabetical order by filename, and store the results in a single backup shadow file. */ rc2 = sortShadowfilesByFilename(); if (rc2 != RC_SUCCESS) { fprintf(stderr, "sortShadowfilesByFilename() returned rc = %d\n", rc2); return(RC_FAIL); } } /* Create the list of files to be processed. */ rc2 = createFilelist(fsName, backupControlP); if (rc2 != RC_SUCCESS) { fprintf(stderr, "createFilelist() returned rc = %d\n", rc2); return(RC_FAIL); } /* Invoke routine to process the transactions file. The true flag that is passed tells the routine to divy the transactions file among the client processes based on aggregate file size, not by number of files, which is what makes sense when doing backups. */ rc2 = processFilelist(fsName, transactionCmdSelective, transactionCmdOption, backupControlP, true); // Create rc as a composite of rc1 and rc2. if (rc1 == RC_FAIL || rc2 == RC_FAIL) { rc = RC_FAIL; } else if (rc1 == RC_PSUCCESS || rc2 == RC_PSUCCESS) { rc = RC_PSUCCESS; } else { rc = RC_SUCCESS; } /* Process the deletions and changes composite return code. */ switch (rc) { case RC_SUCCESS: /* The operation had total success. */ /* We have complete success; delete any pending transactions file left over from a previous partial success. */ unlink(pendingTransactionsList); /* Intentionally fall into the partial success case. */ case RC_PSUCCESS: /* The operation had partial success. */ /* Rename the backup shadow file to be the old backup shadow file. */ tmp = rename(backupShadowFile, backupShadowCheck); if (tmp != 0) { fprintf(stderr, "rename(%s, %s) returned %d\n", backupShadowFile, backupShadowCheck, tmp); return(RC_FAIL); } break; case RC_FAIL: /* The operation failed. */ default: /* The command failed. Cleanup the various files that were created. */ #ifdef DEBUG_BACKUP /* Leave the files as is for failure analysis. */ #else /* We need to do some cleanup here: Delete any existing client transactions files. */ numClients = backupControlP->backupHdr.icf.numberOfClients; for (clientIndex = 0; clientIndex < numClients; clientIndex++) { unlink(clientTransactionsList[clientIndex]); } /* Delete the backup shadow file. */ unlink(backupShadowFile); #endif /* DEBUG_BACKUP */ break; } if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of doFSBackup() ----------------*/ /* * NAME: checkBackupCtrlFile() * * FUNCTION: * Check whether a backup control file exists. * * PARAMETERS: none * * RETURNS: * RC_SUCCESS if file exists, RC_FAIL if it does not. * * NOTES: */ int checkBackupCtrlFile() { Int32 rc; Int32 handle; /* file descriptor for opening a file */ Int32 openFlags; /* flags setting in opening files */ char* fn = "checkBackupCtrlFile"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Determine whether the backup control file exists. The presence of this file indicates the existence of a previous backup. */ openFlags = O_RDONLY; handle = open(fsBackupCtrl, openFlags, 0666); if (handle == -1) // if the open of the backup control file failed { rc = RC_FAIL; if (errno != ENOENT) /* The file exists but it could not be opened. */ { fprintf(stderr, "Unable to open file %s\n", fsBackupCtrl); } } else { rc = RC_SUCCESS; } if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of checkBackupCtrlFile() ----------------*/ /* * Utility functions */ /************************************************************************ * NOTE: The code for these routines is based on equivalent routines * included in the GPFS code for classes. *************************************************************************/ # define BITS_PER_WORD 32 int calcNWords(Int32 size) { return (size + BITS_PER_WORD - 1) / BITS_PER_WORD; } /* * NAME: Bitmap() * * FUNCTION: * Construct a bit map of a specified size and return its address. */ int Bitmap(Int32 size, Bit initialValue, UInt32 **inodeBitsP) { Int32 i, nWords; UInt32 fillWord, endMask; Int32 shift; UInt32* bitsP; /* Compute size of array of UInts, then allocate and initialize it */ assert(size > 0); nWords = calcNWords(size); bitsP = (UInt32 *) malloc(nWords * 4); if (bitsP == NULL) return(-1); if (initialValue == oneBit) fillWord = 0xFFFFFFFF; else fillWord = 0x00000000; for (i = 0 ; i < nWords ; i++) bitsP[i] = fillWord; /* Harbison/Steele C book says: "The result value is also undefined if the value of the right operand is greater than or equal to the width (in bit positions) of the value of the converted left operand." In other words, if you shift an int by 32 bits or more, the result is undefined. */ shift = (BITS_PER_WORD - (size%BITS_PER_WORD)); if (shift == BITS_PER_WORD) endMask = 0xFFFFFFFF; else endMask = 0xFFFFFFFF << shift; bitsP[nWords-1] = fillWord & endMask; *inodeBitsP = bitsP; return(0); } /*------ end of Bitmap() ----------------*/ #if 0 /* * NAME: getValue() * * FUNCTION: * Return the current value of the bit with a given index. * Returns EINVAL if the index is out of range. */ int getValue(UInt32 *inodeBitsP, Int32 index, Bit* bitP) { UInt32 word; UInt32* bitsP; bitsP = inodeBitsP; if (index < 0) return EINVAL; word = bitsP[index/BITS_PER_WORD]; if ((word & (0x80000000 >> (index%BITS_PER_WORD))) == 0x0) *bitP = zeroBit; else *bitP = oneBit; return 0; } /*------ end of getValue() ----------------*/ #endif /* * NAME: testBit() * * FUNCTION: * Test a bit in a bit array. * Returns non-zero if the bit with the given index is a oneBit. * Returns 0 if the index is out of range. */ const UInt32 testBit(UInt32* inodeBitsP, Int32 index) { UInt32 word; UInt32* bitsP; bitsP = inodeBitsP; if (index < 0) { return 0; } word = bitsP[index/BITS_PER_WORD]; return word & (0x80000000 >> (index%BITS_PER_WORD)); } /*------ end of testBit() ----------------*/ /* * NAME: setToZero() * * FUNCTION: * Set the value of a given bit in a bit array to zeroBit. * If oldValueP is not null, the old value of the bit is returned. * Returns EINVAL if the index is out of range. */ int setToZero(inodeBitsArray inodeBitsP, Int32 index, Bit* oldValueP) { UInt32 oldWord; UInt32 mask; UInt32* bitsP; bitsP = inodeBitsP; if (index < 0) { return EINVAL; } oldWord = bitsP[index/BITS_PER_WORD]; mask = 0x80000000 >> (index%BITS_PER_WORD); if ((oldWord & mask) == 0x0) { if (oldValueP != NULL) { *oldValueP = zeroBit; } } else { bitsP[index/BITS_PER_WORD] = oldWord & ~mask; if (oldValueP != NULL) { *oldValueP = oneBit; } } return 0; } /*------ end of setToZero() ----------------*/ /* * NAME: setToOne() * * FUNCTION: * Set the value of a given bit in a bit array to oneBit. * If oldValueP is not null, the old value of the bit is returned. * Returns EINVAL if the index is out of range. */ int setToOne(UInt32* inodeBitsP, Int32 index, Bit* oldValueP) { UInt32 oldWord; UInt32 mask; UInt32* bitsP; bitsP = inodeBitsP; if (index < 0) { return EINVAL; } oldWord = bitsP[index/BITS_PER_WORD]; mask = 0x80000000 >> (index%BITS_PER_WORD); if ((oldWord & mask) == 0x0) { bitsP[index/BITS_PER_WORD] = oldWord | mask; if (oldValueP != NULL) { *oldValueP = zeroBit; } } else { if (oldValueP != NULL) { *oldValueP = oneBit; } } return 0; } /*------ end of setToOne() ----------------*/ /* * NAME: setAllToZero() * * FUNCTION: * Set all the bits of a bit array to zero. */ void setAllToZero(UInt32* bitsP, Int32 size) { Int32 i; Int64 nWords; nWords = calcNWords(size); for (i = 0 ; i < nWords ; i++) { bitsP[i] = 0x0; } } /*------ end of setAllToZero() ----------------*/ /* * NAME: setAllToOne() * * FUNCTION: * Set all the bits of a bit array to one. */ void setAllToOne(UInt32* bitsP, Int32 size) { Int32 i; Int64 nWords; UInt32 endMask; Int32 shift; nWords = calcNWords(size); for (i = 0 ; i < nWords ; i++) { bitsP[i] = 0xFFFFFFFF; } shift = (BITS_PER_WORD - (size%BITS_PER_WORD)); if (shift == BITS_PER_WORD) { endMask = 0xFFFFFFFF; } else { endMask = 0xFFFFFFFF << shift; } bitsP[nWords-1] = 0xFFFFFFFF & endMask; } /*------ end of setAllToOne() ----------------*/ #define tst(a,b) (*mode == 'r'? (b) : (a)) #define RDR 0 #define WTR 1 /* * NAME: pipeOpen() * * FUNCTION: * pipeOpen/pipeClose are taken from the AIX popen/pclose * equivalent. The difference is that pipeOpen() clears * the signal mask in the forked process. * * TODO: This routine needs to handle SIGCLD in an unobtrusive way * so we don't have to set up specific handlers for it * in the callers of this routine. */ FILE* pipeOpen(const char *cmd, const char *mode, pid_t *outPidP) { Int32 p[2]; Int32 myside, yourside; pid_t pid; char* fn = "pipeOpen"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); if (pipe(p) < 0) { return NULL; } myside = tst(p[WTR], p[RDR]); yourside = tst(p[RDR], p[WTR]); if ((pid = fork()) == 0) { Int32 stdio; struct stat statBuf; sigset_t sigMask; /* An exec inherits the signal mask, * so we set up not to block anything anymore. */ sigemptyset(&sigMask); sigprocmask(SIG_SETMASK, &sigMask, NULL); /* myside and yourside reverse roles in child */ stdio = tst(0, 1); close(myside); if (stdio != yourside) { close(stdio); fcntl(yourside, F_DUPFD, stdio); close(yourside); } if (stat(SHELL_PATH, &statBuf) != 0) { exit(127); } execl(SHELL_PATH, SHELL, "-c", cmd, (char *) 0); exit(1); } if (pid < 0) { return NULL; } *outPidP = pid; close(yourside); if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return fdopen(myside, mode); } /*------ end of pipeOpen() ----------------*/ /* * NAME: pipeClose() * */ int pipeClose(FILE *ptr, pid_t pid) { Int32 r = 0; Int32 status; struct sigaction ointact, oquitact, ohupact, ignact; char* fn = "pipeClose"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); ignact.sa_handler = SIG_IGN; sigemptyset(&(ignact.sa_mask)); ignact.sa_flags = 0 ; fclose(ptr); sigaction(SIGHUP, &ignact, &ohupact); sigaction(SIGINT, &ignact, &ointact); sigaction(SIGQUIT, &ignact, &oquitact); if (pid == 0) { status = -1; } else { while ((r = waitpid(pid, &status, 0)) == -1) { if (errno != EINTR) { break; } } } if (r == -1) { status = -1; } sigaction(SIGHUP, &ohupact, NULL); sigaction(SIGINT, &ointact, NULL); sigaction(SIGQUIT, &oquitact, NULL); if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return status; } /*------ end of pipeClose() ----------------*/ /* * NAME: writeBuffer() * * FUNCTION: * Write out to a TransactionList file a specified number of bytes * of a buffer containing records pertinent to the file. * * PARAMETERS: * handle: (IN) The file descriptor indicating the TransactionList file * Buffer: (IN) Points to a buffer area which is to be written out * to the TransactionList file. * size: (IN) The size of the buffer area to be written out. * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: */ int writeBuffer(int handle, transactionsListRecord *Buffer, size_t size) { Int32 rc = RC_SUCCESS; Int32 bytesWritten; /* Write the specified number of bytes from the buffer to the transaction list file. */ bytesWritten = write(handle, Buffer, size); if (bytesWritten != size) { fprintf(stderr, "Error writing to %d; wrote %d bytes, not %d\n", handle, bytesWritten, size); return(RC_FAIL); } return(rc); } /*------ end of writeBuffer() ----------------*/ /* * NAME: processBackupCtrlFile() * * FUNCTION: * Given a filesystem and a backup control file containing information * from a previous tsbackup, store the control information in a backup * control structure in memory and return a pointer to it. * * PARAMETERS: * fsNameP - (IN) Points to the filesystem name. * BackupCtrlFile - (IN) Points to the file containing the pertinent * backup control information. * backupControlHeaderP - (OUT) Points to the gpfs_backup_control_t created * structure. * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: */ int processBackupCtrlFile(char *fsName, char *BackupCtrlFile, gpfs_backup_control_t **backupControlHeaderP) { Int32 rc = RC_SUCCESS; Int32 bytesRead, bytesWritten; char *fsNameP; char *filename; Int64 recordInFile; offset_t nextReadOffset, actualOffset; gpfs_backup_control_t *backupCtrlP; Int32 fp; char* fn = "processBackupCtrlFile"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); filename = BackupCtrlFile; fsNameP = fsName; /* Create gpfs_backup_control structure and initialize it to null characters. */ backupCtrlP = (gpfs_backup_control_t *) malloc(sizeof(gpfs_backup_control_t)); if (backupCtrlP == NULL) { fprintf(stderr, "processBackupCtrlFile(): malloc failure"); *backupControlHeaderP = NULL; /* Let the caller know the malloc failed. */ return(RC_FAIL); } memset((gpfs_backup_control_t *) backupCtrlP, '\0', sizeof(gpfs_backup_control_t)); /* Open the BackupCtrlFile and process its contents to update the gpfs_backup_control_t structure. */ fp = open(filename, O_RDONLY, 0664); if (fp == -1) { fprintf(stderr, "processBackupCtrlFile(): open(%s) failure", filename); return(RC_FAIL); } /* Set the file pointer to the beginning of the file. */ recordInFile = 0; nextReadOffset = recordInFile*sizeof(gpfs_backup_control_t); actualOffset = lseek(fp, nextReadOffset, SEEK_SET); if (actualOffset != nextReadOffset) { fprintf(stderr, "Cannot seek to %lld in %s\n", nextReadOffset, filename); return(RC_FAIL); } /* Read the control data from the file. */ bytesRead = read(fp, backupCtrlP, sizeof(gpfs_backup_control_t)); if (bytesRead != sizeof(gpfs_backup_control_t)) { fprintf(stderr, "Error reading file %s\n", filename); return(RC_FAIL); } /* Close the backup control file. */ close(fp); /* Set the pointer to the data for use by the caller. */ *backupControlHeaderP = backupCtrlP; if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of processBackupCtrlFile() ----------------*/ /* * NAME: forkMsgChild() * * FUNCTION: * Fork a child process that will issue reassuring thoughts periodically * until the master process ends. This is to help the user be patient. * * PARAMETERS: * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: */ int forkMsgChild() { Int32 rc = RC_SUCCESS; char commandBuffer[LINE_LENGTH]; /* cmd to execute */ pid_t pid; char* fn = "forkMsgChild"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Create the command to cause the message child to produce messages. */ snprintf(commandBuffer, sizeof(commandBuffer), "%s/bin/mmexectsmcmd %s %d > /dev/null 2>/dev/null", INSTALL_DIRECTORY, "givestatus", 150); // frequency = 2.5 min /* Create the child process. */ pid = forkit(commandBuffer); /* Check for complete failure (i.e., check whether nothing came back). */ if (pid < 0) { /* The command that was issued failed. Issue an error message and return a bad return code. */ fprintf(stderr, "forkMsgChild(): forkit() returned pid = %d\n", pid); rc = RC_FAIL; } /* Save the pid of the message process so that tsbackup can kill the process when he (tsbackup) is about to exit. */ msgChildPid = pid; if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of forkMsgChild() ----------------*/ /* * NAME: forkit() * * FUNCTION: fork and exec a passed command * */ pid_t forkit(const char *cmd) { pid_t pid; char* fn = "forkit"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); if ((pid = fork()) == 0) { struct stat statBuf; sigset_t sigMask; /* An exec inherits the signal mask, * so we set up not to block anything anymore. */ sigemptyset(&sigMask); sigprocmask(SIG_SETMASK, &sigMask, NULL); if (stat(SHELL_PATH, &statBuf) != 0) { exit(127); } execl(SHELL_PATH, SHELL, "-c", cmd, (char *) 0); exit(1); } if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(pid); } /*------ end of forkit() ----------------*/ /* * NAME: createClientFilelists() * * FUNCTION: * Create the transaction files for the client processes by divying * the transactions file into smaller files containing roughly similar * amounts of backup work. * * PARAMETERS: * backupControlHdrP: (IN) Pointer to a gpfs_backup_control_t structure * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: */ int createClientFilelists(gpfs_backup_control_t *backupControlHdrP) { gpfs_backup_control_t *backupCtrlP; Int64 clientTransListSize[MAX_BACKUP_CLIENT_PROCESSES]; Int64 targetSizeForProcess; Int64 currentSizeForProcess= 0; Int64 sizeOfFilesInBuffer = 0; Int64 smallestSize; Int64 numberOfRecords; Int32 bufferIndex; Int32 processIndex; Int32 numberOfProcesses, np; Int32 i, j, rc = RC_SUCCESS; Int32 smallestIndex; Int32 handle; Int32 bytesRead, bytesWritten; Boolean processHasWork = false; transactionsListRecord transactionRecord; transactionsListRecord buffer[NRECS_PER_BUFFER]; transactionsListRecord *dir_buffers[MAX_DIR_BUFFERS]; char filenameP[21]; char suffix[2]; char *testString; offset_t nextWriteOffset, nextReadOffset, actualOffset; size_t transactionRecordSize; struct stat statBuf; char* fn = "createClientFilelists"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Initializations */ transactionRecordSize = sizeof(transactionsListRecord); backupCtrlP = backupControlHdrP; /* Calculate the number of client processes. */ numberOfProcesses = backupCtrlP->backupHdr.icf.numberOfClients * backupCtrlP->backupHdr.icf.processesPerClient; /* If there is only one client process (possible, but fairly unlikely), just rename the transactions file to TransactionsList_0. */ if (numberOfProcesses == 1) { /* Create the name of the file for the 0th (and only) client process. */ snprintf(clientTransactionsList[0], MAX_FILE_NAME, "%s%d", transactionsList, 0); /* Rename the transactions file. */ rc = rename(transactionsList, clientTransactionsList[0]); if (rc != 0) { fprintf(stderr, "rename(%s, %s) returned rc = %d\n", transactionsList, clientTransactionsList[0], rc); return(RC_FAIL); } } else /* We come here if multiple client processes are needed. */ { /* Create transaction files for the client processes in append mode. The name of each created file will be TransactionsList_L, i.e., we will concatenate the client process suffix L (where L is the client process index) to the existing TransactionsList filename. */ for (j = 0; j < numberOfProcesses; j++) { /* Create the name of the file for the jth client process. */ snprintf(clientTransactionsList[j], MAX_FILE_NAME, "%s%d", transactionsList, j); /* Create the file in append mode. */ handle = open(clientTransactionsList[j], O_CREAT | O_APPEND | O_WRONLY | O_TRUNC, 0644); if (handle == -1) { fprintf(stderr, "Cannot create %s\n", clientTransactionsList[j]); return(RC_FAIL); } else { clientTransactionsListHandle[j] = handle; } /* Set the size of the newly-created list to zero. */ clientTransListSize[j] = 0; } } if (Tracing) traceLine("tsbackup.C", fn, __LINE__); /* If more than one client process is needed (which is most likely), divy the transactions file into as many files as there are processes, each smaller file having a similar total aggregate file size. */ if (numberOfProcesses > 1) { /* Determine the number of records in the transactions file. */ rc = stat(transactionsList, &statBuf); if (rc == -1) { fprintf(stderr, "Could not stat file '%s', errno=%d\n", transactionsList, errno); return(RC_FAIL); } numberOfRecords = statBuf.st_size / transactionRecordSize; /* Open the transactions file in read mode. */ handle = open(transactionsList, O_RDONLY, 0644); if (handle == -1) { fprintf(stderr, "Cannot open %s\n", transactionsList); return(RC_FAIL); } /* If this is a resume backup, doInodeScan() was not called and so the total aggregate file size has not been calculated yet. Calculate it now. */ if (Resume) { /* Position the file handle to point to the first record. */ nextReadOffset = 0; actualOffset = lseek(handle, nextReadOffset, SEEK_SET); if (actualOffset != nextReadOffset) { fprintf(stderr, "Cannot seek to %lld in %s\n", nextReadOffset, transactionsList); return(RC_FAIL); } /* Set the aggregate file size accumulator to zero. */ backupCtrlP->backupHdr.totalSizeOfFiles = 0; /* Loop through the transactions file to calculate the aggregate size. */ while (( bytesRead = read(handle, &transactionRecord, transactionRecordSize)) > 0) { if (bytesRead != transactionRecordSize) { fprintf(stderr, "Error reading file %s\n", transactionsList); return(RC_FAIL); } /* Add the file size to the aggregate file size accumulator. */ backupCtrlP->backupHdr.totalSizeOfFiles += strtoll((const char *) transactionRecord.filesize, NULL, 10); } } /* Position the transactions file handle to point to the first record. */ nextReadOffset = 0; actualOffset = lseek(handle, nextReadOffset, SEEK_SET); if (actualOffset != nextReadOffset) { fprintf(stderr, "Cannot seek to %lld in %s\n", nextReadOffset, transactionsList); return(RC_FAIL); } /* Calculate the target aggregate files size for each client process. */ targetSizeForProcess = backupCtrlP->backupHdr.totalSizeOfFiles / numberOfProcesses; /* Initialize the buffer and process indices. */ bufferIndex = -1; processIndex = 0; np = 0; /* * Loop until all of the transaction records have been processed: * - Read a record from the transaction file. * - Handle the current transaction record as appropriate based on * how full the current client process's transaction file is. * The goal is to give each client process about the same amount * of backup work to do. * - Point to the next client process when the current client * process's transaction file reaches the desired target size. */ while (numberOfRecords > 0) { /* Read the next record from the transaction file. */ bytesRead = read(handle, &transactionRecord, transactionRecordSize); if (bytesRead != transactionRecordSize) { fprintf(stderr, "Error reading file %s\n", transactionsList); return(RC_FAIL); } /* Decrement the number of records. */ numberOfRecords--; /* * Handle the transaction record for the current file. * The record is handled differently based on how the file * fits into the work allotted to the current client process. * There are three cases: * 1. Adding the file would cause the aggregate size to be * beyond the allowed size discretion; * 2. Adding the file would cause the aggregate size to be * over the process limit but within the allowed discretion; * 3. Adding the file would not cause the aggregate size to be * over the process limit. */ if ((currentSizeForProcess + sizeOfFilesInBuffer + strtoll((const char *) transactionRecord.filesize, NULL, 10)) > targetSizeForProcess + PROCESS_OVERFLOW_DISCRETION) { /* * We come here if the aggregate size will be beyond the allowed * discretion. The following steps are performed: * 1. If the buffer has something in it, process the buffer: * a. Write the contents of the buffer to the * client process's transaction file. * b. Set flag indicating the current process file * has been written to. * c. Clear the buffer for use by the next process. * 2. Copy the new transaction record for the file into the * buffer and increment the buffer size accumulator. * 3. If the current process file has been written to, * point to the next client process: * a. Increment the client process index. * b. Reset the process's size accumulator to 0. * c. Reset the flag indicating the current process * has been written to. */ /* If the buffer has something in it, write it to the client process's transaction file. */ if (bufferIndex > -1) { rc = writeBuffer(clientTransactionsListHandle[processIndex], buffer, (bufferIndex + 1) * transactionRecordSize); if (rc != RC_SUCCESS) { fprintf(stderr, "createClientFilelists(): writeBuffer() returned rc = %d\n", rc); return(RC_FAIL); } /* Calculate and store the file size value for this process. */ clientTransListSize[processIndex] = currentSizeForProcess + sizeOfFilesInBuffer; /* Set a flag indicating that the current process file has work. */ processHasWork = true; /* If this is the highest process index so far, save it as the number of processes that have work so far. */ if (processIndex > np) { np = processIndex; } /* Clear the buffer in preparation for reuse. */ memset(buffer, 0, transactionRecordSize*NRECS_PER_BUFFER); sizeOfFilesInBuffer = 0; bufferIndex = -1; } /* Copy the new record for the file into the buffer and increment the buffer size accumulator. */ bufferIndex++; memcpy((void *) &buffer[bufferIndex], (const void *) &transactionRecord, transactionRecordSize); sizeOfFilesInBuffer += strtoll((const char *) transactionRecord.filesize, NULL, 10); if (processHasWork) { /* Select the next process file to add work to. Pick a process whose file is empty or the one that has the most room. */ smallestSize = clientTransListSize[0]; for (j = 0; j < numberOfProcesses; j++) { if (clientTransListSize[j] == 0) { smallestSize = 0; smallestIndex = j; break; } if (clientTransListSize[j] <= smallestSize) { smallestSize = clientTransListSize[j]; smallestIndex = j; } } processIndex = smallestIndex; currentSizeForProcess = smallestSize; /* Reset the "process has work" flag. */ processHasWork = false; } } else if ((currentSizeForProcess + sizeOfFilesInBuffer + strtoll((const char *) transactionRecord.filesize, NULL, 10)) >= targetSizeForProcess) { /* * We come here if the aggregate size will be over the process target * but within the allowed discretion. The following steps are performed: * 1. Copy the new transaction record for the file into the * buffer and increment the buffer size accumulator. * 2. Process the buffer: * a. Write the contents of the buffer to the * client process's transaction file. * b. Clear the buffer for use by the next process. * 3. Point to the next client process: * a. Increment the client process index. * b. Reset the process's size accumulator to 0. * c. Reset the flag indicating the current process * has been written to. */ /* Copy the new record for the file into the buffer and increment the buffer size accumulator. */ bufferIndex++; memcpy((void *) &buffer[bufferIndex], (const void *) &transactionRecord, transactionRecordSize); sizeOfFilesInBuffer += strtoll((const char *) transactionRecord.filesize, NULL, 10); /* Write the contents of the buffer to the process file. */ rc = writeBuffer(clientTransactionsListHandle[processIndex], buffer, (bufferIndex + 1) * transactionRecordSize); if (rc != RC_SUCCESS) { fprintf(stderr, "createClientFilelists(): writeBuffer() returned rc = %d\n", rc); return(RC_FAIL); } /* Calculate and store the file size value for this process. */ clientTransListSize[processIndex] = currentSizeForProcess + sizeOfFilesInBuffer; /* If this is the highest process index so far, save it as the number of processes that have work so far. */ if (processIndex > np) { np = processIndex; } /* Clear the buffer in preparation for reuse. */ memset(buffer, 0, transactionRecordSize*NRECS_PER_BUFFER); sizeOfFilesInBuffer = 0; bufferIndex = -1; /* Select the next process file to add work to. Pick a process whose file is empty or the one that has the most room. */ smallestSize = clientTransListSize[0]; for (j = 0; j < numberOfProcesses; j++) { if (clientTransListSize[j] == 0) { smallestSize = 0; smallestIndex = j; break; } if (clientTransListSize[j] <= smallestSize) { smallestSize = clientTransListSize[j]; smallestIndex = j; } } processIndex = smallestIndex; currentSizeForProcess = smallestSize; /* Reset the "process has work" flag. */ processHasWork = false; } else /* We are within the limits allocated for the process. */ { /* * We come here if the aggregate size will be within the process * target limit. The following steps are performed: * 1. Copy the new transaction record for the file into the * buffer and increment the buffer size accumulator. * 2. If the buffer is full, process the buffer: * a. Write the contents of the buffer to the * client process's transaction file. * b. Increment the aggregate file size for the process. * c. Clear the buffer for use by the next process. */ /* Copy the current transaction record to the buffer. */ bufferIndex++; memcpy((void *) &buffer[bufferIndex], (const void *) &transactionRecord, transactionRecordSize); sizeOfFilesInBuffer += strtoll((const char *) transactionRecord.filesize, NULL, 10); /* If the buffer is full, write its contents to the transaction file for the client process. */ if ((bufferIndex + 1) == NRECS_PER_BUFFER) { /* Write out the buffer and start a new buffer. */ rc = writeBuffer(clientTransactionsListHandle[processIndex], buffer, (bufferIndex + 1) * transactionRecordSize); if (rc != RC_SUCCESS) { fprintf(stderr, "createClientFilelists: writeBuffer() returned rc = %d\n", rc); return(RC_FAIL); } /* Set a flag indicating that the current process file has work. */ processHasWork = true; /* If this is the highest process index so far, save it as the number of processes that have work so far. */ if (processIndex > np) { np = processIndex; } /* Increment the file size value for this process. */ currentSizeForProcess += sizeOfFilesInBuffer; /* Store the file size value for this process. */ clientTransListSize[processIndex] = currentSizeForProcess; /* Clear the buffer in preparation for reuse. */ memset(buffer, 0, transactionRecordSize*NRECS_PER_BUFFER); sizeOfFilesInBuffer = 0; bufferIndex = -1; } } } /* end while (numberOfRecords > 0) */ /* We have finished reading the records in the file. If the buffer has something in it, write it to the client process's transaction file. */ if (bufferIndex > -1) { rc = writeBuffer(clientTransactionsListHandle[processIndex], buffer, (bufferIndex + 1) * transactionRecordSize); if (rc != RC_SUCCESS) { fprintf(stderr, "createClientFilelists: writeBuffer() returned rc = %d\n", rc); return(RC_FAIL); } /* If this is the highest process index so far, save it as the number of processes that have work so far. */ if (processIndex > np) { np = processIndex; } } } else { // Only one process was specified. /* Set the process number variable to 0. */ np = 0; } /* end if (numberOfProcesses > 1) */ if (Tracing) traceLine("tsbackup.C", fn, __LINE__); /* Set the number of processes to the actual number of processes for which we created transaction files. */ backupCtrlP->backupHdr.numberOfProcesses = np + 1; /* If there were multiple processes, close the created client process transaction files and delete any that are empty. */ if (numberOfProcesses > 1) { /* Close the created client process transaction files. */ for (j = 0; j < numberOfProcesses; j++) { rc = close(clientTransactionsListHandle[j]); if (rc != 0) { fprintf(stderr, "Cannot close file %s.\n", clientTransactionsList[j]); return(RC_FAIL); } } /* Delete any created client process transactions files that are empty. */ for (j = np+1; j < numberOfProcesses; j++) { /* Delete the empty client process transactions file. */ unlink(clientTransactionsList[j]); } } /* end if (numberOfProcesses > 1) */ if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of createClientFilelists() ----------------*/ /* * NAME: createClientFilelists2() * * FUNCTION: * Create the transaction files for the client processes by divying * the transactions file into smaller files containing roughly similar * numbers of records. * * PARAMETERS: * backupControlHdrP: (IN) Pointer to a gpfs_backup_control_t structure * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: */ int createClientFilelists2(gpfs_backup_control_t *backupControlHdrP) { gpfs_backup_control_t *backupCtrlP; Int64 targetSizeForProcess; Int64 currentSizeForProcess= 0; Int64 sizeOfFilesInBuffer = 0; Int64 numberOfRecords, nrecs; Int32 bufferIndex; Int32 processIndex; Int32 numberOfProcesses, np, numberPerProcess; Int32 i, j, rc = RC_SUCCESS; Int32 handle; Int32 bytesRead, bytesWritten; Boolean processHasWork = false; transactionsListRecord transactionRecord; transactionsListRecord buffer[NRECS_PER_BUFFER]; transactionsListRecord *dir_buffers[MAX_DIR_BUFFERS]; char filenameP[21]; char suffix[2]; char *testString; offset_t nextWriteOffset, nextReadOffset, actualOffset; size_t transactionRecordSize; struct stat statBuf; char* fn = "createClientFilelists2"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Initializations */ transactionRecordSize = sizeof(transactionsListRecord); backupCtrlP = backupControlHdrP; /* Calculate the number of client processes. */ numberOfProcesses = backupCtrlP->backupHdr.icf.numberOfClients * backupCtrlP->backupHdr.icf.processesPerClient; /* If there is only one client process (possible, but fairly unlikely), just rename the transactions file to TransactionsList_0. */ if (numberOfProcesses == 1) { /* Create the name of the file for the 0th (and only) client process. */ snprintf(clientTransactionsList[0], MAX_FILE_NAME, "%s%d", transactionsList, 0); /* Rename the transactions file. */ rc = rename(transactionsList, clientTransactionsList[0]); if (rc != 0) { fprintf(stderr, "rename(%s, %s) returned rc = %d\n", transactionsList, clientTransactionsList[0], rc); return(RC_FAIL); } } else /* We come here if multiple client processes are needed. */ { /* Create transaction files for the client processes in append mode. The name of each created file will be TransactionsList_L, i.e., we will concatenate the client process suffix L (where L is the client process index) to the existing TransactionsList filename. */ for (j = 0; j < numberOfProcesses; j++) { /* Create the name of the file for the jth client process. */ snprintf(clientTransactionsList[j], MAX_FILE_NAME, "%s%d", transactionsList, j); /* Create the file in append mode. */ handle = open(clientTransactionsList[j], O_CREAT | O_APPEND | O_WRONLY | O_TRUNC, 0644); if (handle == -1) { fprintf(stderr, "Cannot create %s\n", clientTransactionsList[j]); return(RC_FAIL); } else { clientTransactionsListHandle[j] = handle; } } } if (Tracing) traceLine("tsbackup.C", fn, __LINE__); /* If more than one client process is needed (which is most likely), divy the transactions file into as many files as there are processes, each smaller file having the same number of files (or nearly so). */ if (numberOfProcesses > 1) { /* Determine the number of records in the transactions file. */ rc = stat(transactionsList, &statBuf); if (rc == -1) { fprintf(stderr, "Could not stat file '%s', errno=%d\n", transactionsList, errno); return(RC_FAIL); } numberOfRecords = statBuf.st_size / transactionRecordSize; numberPerProcess = numberOfRecords / (backupCtrlP->backupHdr.numberOfProcesses); /* Open the transactions file in read mode. */ handle = open(transactionsList, O_RDONLY, 0644); if (handle == -1) { fprintf(stderr, "Cannot open %s\n", transactionsList); return(RC_FAIL); } /* Position the transactions file handle to point to the first record. */ nextReadOffset = 0; actualOffset = lseek(handle, nextReadOffset, SEEK_SET); if (actualOffset != nextReadOffset) { fprintf(stderr, "Cannot seek to %lld in %s\n", nextReadOffset, transactionsList); return(RC_FAIL); } /* Initialize the buffer and process indices. */ bufferIndex = -1; processIndex = 0; nrecs = 0; /* * Loop until all of the transaction records have been processed: * - Read a record from the transaction file. * - Handle the current transaction record as appropriate based on * how full the current client process's transaction file is. * The goal is to give each client process about the same amount * of backup work to do. * - Point to the next client process when the current client * process's transaction file reaches the desired target size. */ while (numberOfRecords > 0) { /* Read the next record from the transaction file. */ bytesRead = read(handle, &transactionRecord, transactionRecordSize); if (bytesRead != transactionRecordSize) { fprintf(stderr, "Error reading file %s\n", transactionsList); return(RC_FAIL); } /* Decrement the total number of records and increment the number of records for the current process. */ numberOfRecords--; nrecs++; /* If we are filling the transaction file for the last client process, go right to the code to write the tranaction record appropriately. */ if (processIndex == numberOfProcesses - 1) { goto put_record2; } /* * Handle the transaction record for the current file. * The record is handled differently based on how the file * fits into work allotted to the current client process. * There are two cases: * 1. Adding the file would cause the client file to have * more records than the target number of records; * 2. Adding the file would not cause the client file to have * more records than the target number of records; */ if (nrecs >= numberPerProcess) { /* * We come here if adding the file would cause the client file * to have more records than the target. The following steps * are performed: * 1. If the buffer has something in it, process the buffer: * a. Write the contents of the buffer to the * client process's transaction file. * b. Set flag indicating the current process file * has been written to. * c. Clear the buffer for use by the next process. * 2. Copy the new transaction record for the file into the * buffer and increment the buffer size accumulator. * 3. If the current process file has been written to, * point to the next client process: * a. Increment the client process index. * b. Reset the process's size accumulator to 0. * c. Reset the flag indicating the current process * has been written to. */ /* If the buffer has something in it, write it to the client process's transaction file. */ if (bufferIndex > -1) { rc = writeBuffer(clientTransactionsListHandle[processIndex], buffer, (bufferIndex + 1) * transactionRecordSize); if (rc != RC_SUCCESS) { fprintf(stderr, "createClientFilelists2(): writeBuffer() returned rc = %d\n", rc); return(RC_FAIL); } /* Set a flag indicating that the current process file has work. */ processHasWork = true; /* Save the process index as the number of processes that have work so far. */ np = processIndex; /* Clear the buffer in preparation for reuse. */ memset(buffer, 0, transactionRecordSize*NRECS_PER_BUFFER); bufferIndex = -1; } /* Copy the new record for the file into the buffer and increment the buffer size accumulator. */ bufferIndex++; memcpy((void *) &buffer[bufferIndex], (const void *) &transactionRecord, transactionRecordSize); if (processHasWork) { /* Point to the next client process and set its size to zero. */ processIndex++; nrecs = 0; /* Reset the "process has work" flag. */ processHasWork = false; } } else /* We are within the limits of the size allocated for the server. */ { /* * We come here if adding the file would not cause the client file * to have more records than the target. The following steps * are performed: * 1. Copy the new transaction record for the file into the * buffer and increment the buffer size accumulator. * 2. If the buffer is full, process the buffer: * a. Write the contents of the buffer to the * client process's transaction file. * b. Clear the buffer for use by the next process. */ /* Copy the current transaction record to the buffer. */ put_record2: bufferIndex++; memcpy((void *) &buffer[bufferIndex], (const void *) &transactionRecord, transactionRecordSize); /* If the buffer is full, write its contents to the transaction file for the client process. */ if ((bufferIndex + 1) == NRECS_PER_BUFFER) { /* Write out the buffer and start a new buffer. */ rc = writeBuffer(clientTransactionsListHandle[processIndex], buffer, (bufferIndex + 1) * transactionRecordSize); if (rc != RC_SUCCESS) { fprintf(stderr, "createClientFilelists2: writeBuffer() returned rc = %d\n", rc); return(RC_FAIL); } /* Set a flag indicating that the current process file has work. */ processHasWork = true; /* Save the process index as the number of processes that have work so far. */ np = processIndex; /* Clear the buffer in preparation for reuse. */ memset(buffer, 0, transactionRecordSize*NRECS_PER_BUFFER); bufferIndex = -1; } } } /* end while (numberOfRecords > 0) */ /* We have finished reading the records in the file. * If the buffer has something in it, write it to the client process's * transaction file. */ if (bufferIndex > -1) { rc = writeBuffer(clientTransactionsListHandle[processIndex], buffer, (bufferIndex + 1) * transactionRecordSize); if (rc != RC_SUCCESS) { fprintf(stderr, "createClientFilelists2: writeBuffer() returned rc = %d\n", rc); return(RC_FAIL); } /* Save the process index as the number of processes that have work so far. */ np = processIndex; } } else { // Only one process was specified. /* Set the process number variable to 0. */ np = 0; } /* end if (numberOfProcesses > 1) */ if (Tracing) traceLine("tsbackup.C", fn, __LINE__); /* Set the number of processes to the actual number of processes for which we created transaction files. */ backupCtrlP->backupHdr.numberOfProcesses = np + 1; /* If there were multiple processes, close the created client process transaction files and delete any that are empty. */ if (numberOfProcesses > 1) { /* Close the created client process transaction files. */ for (j = 0; j < numberOfProcesses; j++) { rc = close(clientTransactionsListHandle[j]); if (rc != 0) { fprintf(stderr, "Cannot close file %s.\n", clientTransactionsList[j]); return(RC_FAIL); } } /* Delete any created client process transactions files that are empty. */ for (j = np+1; j < numberOfProcesses; j++) { /* Delete the empty client process transactions file. */ unlink(clientTransactionsList[j]); } } /* end if (numberOfProcesses > 1) */ if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of createClientFilelists2() ----------------*/ /* * NAME: createBackupSnapshot() * * FUNCTION: * Create a snapshot of a GPFS filesystem and store it at * the mountpoint of the filesystem. * * PARAMETERS: * fsName - (IN) Pointer to the device of the filesystem * for which to get a snapshot * snapshotName - (IN) snapshot name * * RETURNS: * Errno * * NOTES: */ int createBackupSnapshot(char *deviceName, char *snapshotName) { Int32 rc = RC_SUCCESS; struct stat statBuf; char* fn = "createBackupSnapshot"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Obtain a handle to access the active filesystem. */ fsSnapHandleP = gpfs_get_fssnaphandle_by_name(deviceName, NULL); if (fsSnapHandleP == NULL) { rc = errno; fprintf(stderr, "%s: gpfs_get_fssnaphandle_by_name(%s, %s): %s\n", fn, deviceName, "NULL", strerror(rc)); return(RC_FAIL); } /* Use the handle to obtain the name of the subdirectory in which snapshots would be created. */ rc = gpfs_get_snapdirname(fsSnapHandleP, snapshotSubdir, MAX_FILE_NAME); if (rc == -1) { rc = errno; fprintf(stderr, "%s: gpfs_get_snapdirname(%s, %s, %d): %s\n", fn, fsSnapHandleP, snapshotSubdir, MAX_FILE_NAME, strerror(rc)); return(RC_FAIL); } /* Determine whether there would be a name collision if a snapshot were created. If so, issue an error message and fail. */ sprintf(sstring, "%s/%s", fsName, snapshotSubdir); rc = stat(sstring, &statBuf); if (rc == -1) { /* It is acceptable for the snapshot directory to not exist; Otherwise, issue an error and fail. */ if (errno != ENOENT) { fprintf(stderr, "Could not stat directory '%s', errno=%d\n", sstring, errno); return(RC_FAIL); } } else { /* If we get here, the subdirectory exists. That is ok if it is the special subdirectory for snapshots; to recognize this we use the fact that the snapshot subdirectory always has an inode number whose value is 0xFFFF0000 (best test we have right now, per Frank). Otherwise, issue an error message stating we have a name collision and then fail the routine. */ if (statBuf.st_ino != 0xFFFF0000) { fprintf(stderr, "tsbackup: Subdirectory %s already exists. Unable to create snapshot.\n", sstring); return(RC_FAIL); } /* Delete the backup snapshot if it exists. */ sprintf(sstring, "%s/%s", sstring, snapshotName); if (stat(sstring, &statBuf) != -1) { /* Delete the snapshot. */ sprintf(sstring, "/usr/lpp/mmfs/bin/tsdelsnapshot %s %s > /dev/null", deviceName, snapshotName); rc = system(sstring); /* Check system() return. */ if (rc != 0) { fprintf(stderr, "system(%s) returned rc = %d\n", sstring, rc); return(RC_FAIL); } } } /* Issue the tscrsnapshot command to create a snapshot and store it at the mount point of the filesystem. */ sprintf(sstring, "/usr/lpp/mmfs/bin/tscrsnapshot %s %s > /dev/null", deviceName, snapshotName); rc = system(sstring); /* check system() return */ if (rc != 0) { fprintf(stderr, "system(%s) returned rc = %d\n", sstring, rc); // BCH - possible candidate for message catalog fprintf(stderr, "Creation of filesystem snapshot did not succeed.\n"); return(RC_FAIL); } /* Create fsSnapshotPathname now that we have a snapshot. Get the snapshot handle for the snapshot and then use the handle to get the full pathname of the snapshot. */ fsSnapHandleP = gpfs_get_fssnaphandle_by_name(deviceName, fsSnapshotName); if (fsSnapHandleP == NULL) { rc = errno; fprintf(stderr, "%s: gpfs_get_fssnaphandle_by_name(%s, %s): %s\n", fn, deviceName, fsSnapshotName, strerror(rc)); return(RC_FAIL); } /* Use the handle to get the full pathname of the snapshot. */ snapshotPathname = gpfs_get_pathname_from_fssnaphandle(fsSnapHandleP); if (snapshotPathname == NULL) { rc = errno; fprintf(stderr, "%s: gpfs_get_pathname_from_fssnaphandle(%s): %s\n", fn, fsSnapHandleP, strerror(rc)); return(RC_FAIL); } strcpy(fsSnapshotPathname, snapshotPathname); if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of createBackupSnapshot() ----------------*/ /* * NAME: processInputCtrlFile() * * FUNCTION: * Process the input control file (the file passed via the -F parameter * of mmbackup) which contains the information for controlling the backup. * * PARAMETERS: * fsNameP - (IN) pointer to the filesystem mount point * inputCtrlFile - (IN) pointer to the file containing the pertinent * TSM client and server node information * backupControlHeaderP - (OUT) pointer to the gpfs_backup_control_t created * structure; the header info in this structure * contains the information regarding the nodes * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: * * Input file format: (comments begin with a # character in the 1st column) * ========================================================================== * # Input control file for mmbackup * # name of Tivoli server (must match name in the TSM client's dsm.opt file) * serverName = c164n06.ppd.pok.ibm.com * # the following value is used to specify parallelism on each client * numberOfProcessesPerClient = 4 * # backup client nodes * clientName = c148n11 * clientName = c148n13 * clientName = c148n15 * */ int processInputCtrlFile(char *fsName, char *inputCtrlFile, gpfs_backup_control_t **backupControlHeaderP, Boolean doMalloc) { Int32 j, rc = RC_SUCCESS; Int32 numProcessesPerClient; Int32 clientIndex = 0; Int32 linectr = 0; char *fsNameP; char *filename; gpfs_backup_control_t *backupCtrlP; FILE *fp; char* eq_p; char linein[LINE_LENGTH]; char numberOfProcessesPerClientString[27] = "numberOfProcessesPerClient"; char serverNameString[11] = "serverName"; char clientNameString[11] = "clientName"; Boolean foundServer = false; Boolean foundNumberofProcessesPerClient = false; char* fn = "processInputCtrlFile"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); filename = inputCtrlFile; fsNameP = fsName; if (doMalloc) { /* Create gpfs_backup_control structure and initialize it to null characters. */ backupCtrlP = (gpfs_backup_control_t *) malloc(sizeof(gpfs_backup_control_t)); if (backupCtrlP == NULL) { fprintf(stderr, "processInputCtrlFile(): malloc failure"); *backupControlHeaderP = NULL; /* Let caller know the malloc failed. */ return(RC_FAIL); } memset((gpfs_backup_control_t *) backupCtrlP, '\0', sizeof(gpfs_backup_control_t)); /* Pass back to the caller a pointer to the backup control data. */ *backupControlHeaderP = backupCtrlP; } else { /* The control block already exists in storage. Set our pointer so that we can update it. */ backupCtrlP = *backupControlHeaderP; } backupCtrlP->backupHdr.icf.numberOfClients = 0; /* Open the input control file. */ if ((fp = fopen(filename, "r")) == (FILE *) NULL) { fprintf(stderr, "processInputCtrlFile(): fopen(%s) failed\n", filename); return(RC_FAIL); } /* Get the next line of information. */ while (fgets(linein, sizeof(linein), fp) != NULL) { /* Increment the line counter. */ linectr++; /* Check the length of the input line. */ if (strlen(linein) == ((LINE_LENGTH) - 1)) { fprintf(stderr, "File %s line %d: Line too long: \n %s\n", filename, linectr, linein); return(RC_FAIL); } /* Zap any trailing new line character. */ if (linein[strlen(linein)-1] == '\n') { linein[strlen(linein)-1] = '\0'; } /* Skip blank or comment lines. */ if ( (strlen(linein) == 0) || (linein[0] == '\0') || (linein[0] == '#') ) { continue; } /* If no '=' character was found, this is a bogus line. */ if ((eq_p = strchr(linein, inputCtrlSeperatorChar)) == NULL) { fprintf(stderr, "File %s line %d: No %c character in line\n %s\n", filename, linectr, inputCtrlSeperatorChar, linein); return(RC_FAIL); } else { /* Check for the various allowable types of lines. */ /* Is this line specifying the server? */ if (strncmp(linein, serverNameString, (strlen(serverNameString))) == 0) { /* Issue an error if the server name was already specified. */ if (foundServer) { fprintf(stderr, "File %s line %d: server name was specified more than once:\n %s\n", filename, linectr, linein); return(RC_FAIL); } foundServer = true; /* Copy the server name from the input line to the control structure. */ eq_p++; strcpy(backupCtrlP->backupHdr.icf.serverName, eq_p); continue; } else { /* Is this line specifying the number of processes per client? */ if (strncmp(linein, numberOfProcessesPerClientString, (strlen(numberOfProcessesPerClientString))) == 0) { /* Issue an error if the number of processes per client was already specified. */ if (foundNumberofProcessesPerClient == true) { fprintf(stderr, "File %s line %d: number of processes per client was specified more than once\n %s\n", filename, linectr, linein); return(RC_FAIL); } foundNumberofProcessesPerClient = true; /* Copy the number of processes per client from the input line to the control structure. */ eq_p++; numProcessesPerClient = atoi((const char *) eq_p); backupCtrlP->backupHdr.icf.processesPerClient = numProcessesPerClient; if ((numProcessesPerClient <= 0) || (numProcessesPerClient > MAX_BACKUP_CLIENT_PROCESSES)) { fprintf(stderr, "The %s value (%d) specified in file %s is not valid.\n", numberOfProcessesPerClientString, numProcessesPerClient, filename); fclose(fp); return(RC_FAIL); } continue; } else { /* Is this line specifying a client? */ if (strncmp(linein, clientNameString, (strlen(clientNameString))) == 0) { /* Copy the client name from the input line to the control structure. */ eq_p++; strcpy(backupCtrlP->backupHdr.icf.clients[clientIndex].clientName, eq_p); // BCH - Is the following loc necessary with only 1 server? backupCtrlP->backupHdr.icf.clients[clientIndex].clientIndex = clientIndex; clientIndex++; continue; } else { /* Unrecognized line. Issue error and return with failure rc. */ fprintf(stderr, "File %s line %d: Line not recognized:\n %s\n", filename, linectr, linein); return(RC_FAIL); } } } } } // end while (fgets(linein, sizeof(linein), fp) != NULL) if (clientIndex == 0) { fprintf(stderr, "File %s specified 0 clients.\n", filename); fclose(fp); return(RC_FAIL); } if (clientIndex > MAX_BACKUP_CLIENTS) { fprintf(stderr, "File %s specified %d clients but only %d are allowed.\n", filename, clientIndex, MAX_BACKUP_CLIENTS); fclose(fp); return(RC_FAIL); } /* Save the number of clients into the control structure. */ backupCtrlP->backupHdr.icf.numberOfClients = clientIndex; /* If the number of processes per client was not specified, set it to 1. */ if (foundNumberofProcessesPerClient == false) { backupCtrlP->backupHdr.icf.processesPerClient = 1; } /* Close the input control file. */ fclose(fp); if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of processInputCtrlFile() ----------------*/ /* * NAME: createBackupCtrlFile() * * FUNCTION: * Create the .backup_control file and write out its contents. * Release the storage of the pertinent gpfs_backup_control_t structure. * * PARAMETERS: * FileName - (IN) Name of the file to be created. * backupControlHeaderP - (IN) Points to the gpfs_backup_control_t created * structure. The header info in this structure * contains the information regarding the nodes. * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: */ int createBackupCtrlFile(char *fileName, gpfs_backup_control_t *backupControlHeaderP) { Int32 fileHandle, rc = RC_SUCCESS; char *fileNameP; Int32 fileIndex; Int32 bytesRead, bytesWritten; Int64 nextWriteOffset, actualOffset, recordInFile; gpfs_backup_control_t *backupCtrlP; size_t ctrlfileRecordSize = sizeof(gpfs_backup_control_t); char* fn = "createBackupCtrlFile"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); fileNameP = fileName; backupCtrlP = backupControlHeaderP; /* Create the .backup_control file. */ fileHandle = open(fileNameP, O_CREAT | O_APPEND | O_RDWR | O_TRUNC, 0600); if (fileHandle == -1) { fprintf(stderr, "Cannot open file %s\n", fileNameP); return(RC_FAIL); } recordInFile = 0; nextWriteOffset = recordInFile*ctrlfileRecordSize; actualOffset = lseek(fileHandle, nextWriteOffset, SEEK_SET); if (actualOffset != nextWriteOffset) { fprintf(stderr, "Cannot seek to %lld in %s\n", recordInFile*ctrlfileRecordSize, fileNameP); return(RC_FAIL); } bytesWritten = write(fileHandle, backupCtrlP, ctrlfileRecordSize); if (bytesWritten != ctrlfileRecordSize) { fprintf(stderr, "Error writing file %s\n", fileNameP); return(RC_FAIL); } rc = close(fileHandle); if (rc != 0) { fprintf(stderr, "Cannot close file descriptor fileHandle = %d\n", fileHandle); return(RC_FAIL); } if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of createBackupCtrlFile() ----------------*/ /* * NAME: doInodeScan() * * FUNCTION: * Do an inodescan on a snapshot of a GPFS filesystem, * and store the results as follows: * a) Create a bit array where an index in the array indicates * the inode number and the value whether or not the inode needs * to be operated on (a value of 1 indicates a yes answer). * b) Capture in a temporary filesizes file, one line per inode, * indicating the inode number and the size of its file. * c) Determine the total number of inodes used by the filesystem, * the maximum inode number used, and the aggregate size of all the * files of the filesystem needing to be operated on. * * PARAMETERS: * fsName - (IN) pointer to the filesystem name for which to * do an inodescan * inodeBitsArrayP - (OUT) pointer to the bit map array created * backupControlHdrP - (IN) pointer to the gpfs_backup_control_t created * structure; the header info in this structure * contains the information in (c) above * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: */ int doInodeScan(char *fsName, inodeBitsArray *inodeBitsArrayP, gpfs_backup_control_t *backupControlHdrP) { gpfs_iscan_t *backupIscan; gpfs_ino_t lastInodeNum = 0; gpfs_ino_t maxInodeNum; gpfs_backup_control_t *backupCtrlP; gpfs_fssnap_id_t backup_snapID; Int32 rc = RC_SUCCESS; Int32 bytesWritten; inodeBitsArray InodeP; const gpfs_iattr_t *iattrP; backup_filesizes_t inodeSize; char inode_num[MAX_INT32_CHARS]; char inode_size[MAX_INT64_CHARS]; char* fn = "doInodeScan"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); backupCtrlP = backupControlHdrP; /* Create a bit map array for a GPFS filesystem and initialize it to zero bits. */ rc = Bitmap(MAX_INODES, zeroBit, &InodeP); if (rc != 0) { fprintf(stderr, "Bitmap error rc = %x\n", rc); return(RC_FAIL); } /* Create the filesizes file. */ filesizesHandle = open(filesizesFile, O_CREAT | O_APPEND | O_RDWR | O_TRUNC, 0600); if (filesizesHandle == -1) { fprintf(stderr, "Cannot open temp file %s\n", filesizesFile); free(InodeP); return(RC_FAIL); } /* initializations */ backupCtrlP->backupHdr.inodesTotal = 0; backupCtrlP->backupHdr.totalSizeOfFiles = 0; /* Get the snapshot handle for this filesystem. */ fsSnapHandleP = gpfs_get_fssnaphandle_by_path(fsName); if (fsSnapHandleP == NULL) { rc = errno; fprintf(stderr, "%s: gpfs_get_fssnaphandle_by_path(%s): %s\n", "doInodeScan", fsName, strerror(rc)); return(RC_FAIL); } /* Open the inode scan for this filesystem. Passing a NULL for the 2nd parameter (previous snapshot id) causes gpfs_open_inodescan() to return the inodes for all the files in the filesystem. */ backupIscan = gpfs_open_inodescan(fsSnapHandleP, NULL, &maxInodeNum); /* If gpfs_open_inodescan() failed, issue an error and quit. */ if (backupIscan == NULL) { rc = errno; fprintf(stderr, "%s: gpfs_open_inodescan() for snapshot %s failed: %s\n", "doInodeScan", fsName, strerror(rc)); free(InodeP); return(RC_FAIL); } /* Get the snapshot id and save it in the backup control file. */ rc = gpfs_get_fssnapid_from_fssnaphandle(fsSnapHandleP, &backup_snapID); if (rc != 0) { rc = errno; fprintf(stderr, "%s: gpfs_get_fssnapid_from_fssnaphandle(fsSnapHandleP, &snapID): %s\n", "doInodeScan", strerror(rc)); return(RC_FAIL); } memcpy((void *) &(backupCtrlP->backupHdr.snapshotId), (const void *) &backup_snapID, sizeof(gpfs_fssnap_id_t)); /* Use the backup/restore by inode API to do an inode scan and get the list of inodes into the bit array. Loop doing gpfs_next_inode() which returns gpfs_iattr_t and put the inode numbers into the allocated array. */ /* Repeated calls to gpfs_next_inode() return inode information about all existing files and directories in inode number order. */ while (1) { rc = gpfs_next_inode(backupIscan, maxInodeNum, &iattrP); if (rc != 0) { rc = errno; fprintf(stderr, "%s: gpfs_next_inode(%s,%d): %s\n", "doInodeScan", fsName, lastInodeNum, strerror(rc)); return(RC_FAIL); } /* Exit the loop if a null pointer, the indication for the end of the inode scan, is encountered. */ if (iattrP == NULL) { break; } /* Save the inode number. */ lastInodeNum = iattrP->ia_inode; /* Set the bit in the bit array. */ rc = setToOne(InodeP, (UInt32) iattrP->ia_inode, NULL); assert(rc == 0); /* Construct a record for the backup filesizes file and append it to the file. Note the use of sprintf and memcpy instead of sprintf alone to avoid copying the null at the end of a string into the record. This is done because sort() has trouble handling such embedded nulls. */ memset((void *) &inodeSize, ' ', sizeof(backup_filesizes_t)); sprintf(inode_num, "%d", iattrP->ia_inode); memcpy((void *) &inodeSize.inodenum, (const void *) &inode_num, strlen(inode_num)); sprintf(inode_size, "%lld", iattrP->ia_size); memcpy((void *) &inodeSize.filesize, (const void *) &inode_size, strlen(inode_size)); inodeSize.newline_char = '\n'; bytesWritten = write(filesizesHandle, &inodeSize, sizeof(backup_filesizes_t)); if (bytesWritten != sizeof(backup_filesizes_t)) { fprintf(stderr, "Error writing to filesizes file\n"); free(InodeP); return(RC_FAIL); } /* Update pertinent fields in the backup header. */ backupCtrlP->backupHdr.totalSizeOfFiles += (UInt32) iattrP->ia_size; backupCtrlP->backupHdr.inodeMax = (UInt32) iattrP->ia_inode; backupCtrlP->backupHdr.inodesTotal++; } // end while (1) rc = close(filesizesHandle); if (rc != 0) { fprintf(stderr, "Error in closing the filesizes file\n"); free(InodeP); return(RC_FAIL); } /* Close the inode scan. */ gpfs_close_inodescan(backupIscan); /* Free the snapshot handle. */ gpfs_free_fssnaphandle(fsSnapHandleP); /* Set the bit array pointer being passed back. */ *inodeBitsArrayP = InodeP; if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of doInodeScan() ----------------*/ /* * NAME: doIncrInodeScan() * * FUNCTION: * Do an inodescan on a snapshot of a GPFS filesystem, * and capture the results as follows: * a) Create a bit array where an index in the array indicates the * inode number and the value indicates whether or not the inode is * for a file that is new or has changed since the previous snapshot * b) Determine the total number of inodes used by the filesystem, * and the maximum inode number used. * * PARAMETERS: * fsName - (IN) pointer to the filesystem name for which to * do an inodescan * prevSnapIdP - (IN) pointer to the snapshot ID of a previous snapshot * inodeBitsArray2P - (OUT) pointer to the bit map array created * backupControlHdrP - (IN) pointer to the gpfs_backup_control_t created * structure; the header info in this structure * contains the information in (b) above * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: */ int doIncrInodeScan(char *fsName, gpfs_fssnap_id_t *prevSnapIdP, inodeBitsArray *inodeBitsArray2P, gpfs_backup_control_t *backupControlHdrP) { gpfs_iscan_t *backupIscan2; gpfs_ino_t lastInodeNum = 0; gpfs_ino_t maxInodeNum; gpfs_backup_control_t *backupCtrlP; gpfs_fssnap_id_t backup_snapID; Int32 rc = RC_SUCCESS; Int32 bytesWritten; inodeBitsArray inode2P; const gpfs_iattr_t *iattrP; char inode_num[MAX_INT32_CHARS]; char inode_size[MAX_INT64_CHARS]; char* fn = "doIncrInodeScan"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Create a bit map array for a GPFS filesystem and initialize it to zero bits. */ rc = Bitmap(MAX_INODES, zeroBit, &inode2P); if (rc != 0) { fprintf(stderr, "Bitmap error rc = %x\n", rc); return(RC_FAIL); } /* Get the snapshot handle for this filesystem. */ fsSnapHandleP = gpfs_get_fssnaphandle_by_path(fsName); if (fsSnapHandleP == NULL) { rc = errno; fprintf(stderr, "%s: gpfs_get_fssnaphandle_by_path(%s): %s\n", "doIncrInodeScan", fsName, strerror(rc)); return(RC_FAIL); } /* Open an incremental inode scan for this filesystem. Passing the id of the previous snapshot causes gpfs_open_inodescan() to only return the inodes for files that have been added or changed since the previous snapshot, plus those that existed in the previous snapshot but no longer exist now (i.e., those for files which were deleted from the filesystem since the previous snapshot). */ backupIscan2 = gpfs_open_inodescan(fsSnapHandleP, prevSnapIdP, &maxInodeNum); /* If gpfs_open_inodescan() failed, issue an error and quit. */ if (backupIscan2 == NULL) { rc = errno; fprintf(stderr, "%s: gpfs_open_inodescan() for snapshot %s failed: %s\n", "doIncrInodeScan", fsName, strerror(rc)); free(inode2P); return(RC_FAIL); } /* Use the backup/restore by inode API to do an inode scan and get the list of inodes into the bit array. Loop doing gpfs_next_inode() which returns gpfs_iattr_t and put the inode numbers into the allocated array. */ /* Repeated calls to gpfs_next_inode() return inode information about all existing files and directories in inode number order. */ while (1) { rc = gpfs_next_inode(backupIscan2, maxInodeNum, &iattrP); if (rc != 0) { rc = errno; fprintf(stderr, "%s: gpfs_next_inode(%s,%d): %s\n", "doIncrInodeScan", fsName, lastInodeNum, strerror(rc)); return(RC_FAIL); } /* Exit the loop if a null pointer, the indication for the end of the inode scan, is encountered. */ if (iattrP == NULL) { break; } /* Save the inode number. */ lastInodeNum = iattrP->ia_inode; /* If this inode for a file that is still present, flag it. (We don't flag inodes for deleted files.) */ if (iattrP->ia_nlink != 0) { rc = setToOne(inode2P, (UInt32) iattrP->ia_inode, NULL); assert(rc == 0); } } // end while (1) /* Close the inode scan. */ gpfs_close_inodescan(backupIscan2); /* Free the snapshot handle. */ gpfs_free_fssnaphandle(fsSnapHandleP); /* Set the bit array pointers being passed back. */ *inodeBitsArray2P = inode2P; if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of doIncrInodeScan() ----------------*/ /* * NAME: EnqueueWork() * * Add an element to the work queue and signal any thread waiting for work. */ int EnqueueWork(const char *pathP, const char *dirP, ino_t inode) { int rc; int pathLen = strlen(pathP); QueueElement *qP = NULL; /* Allocate a work queue element and space for the path. */ qP = (QueueElement *) malloc((sizeof(*qP) - sizeof(qP->path)) + pathLen + strlen(dirP) + 2); if (qP == NULL) { fprintf(stderr, "Out of memory.\n"); return(ENOMEM); } /* Initialize element. */ qP->ino = inode; if (pathLen > 0) { snprintf(qP->path, MAX_FILE_NAME, "%s/%s", pathP, dirP); } else { strcpy(qP->path, dirP); } /* Acquire mutex protecting the work queue. */ rc = pthread_mutex_lock(&QueueMutex); if (rc != 0) { fprintf(stderr, "Error in pthread_mutex_lock: %s\n", strerror(rc)); return rc; } /* Add work element to the list. */ qP->nextP = WorkQueueP; WorkQueueP = qP; /* Signal a waiting worker thread. */ if (NWorkersWaiting > 0) { NWorkersWaiting -= 1; rc = pthread_cond_signal(&QueueCond); if (rc != 0) { fprintf(stderr, "Error in pthread_cond_signal: %s\n", strerror(rc)); return rc; } } /* Release mutex before returning. */ rc = pthread_mutex_unlock(&QueueMutex); if (rc != 0) { fprintf(stderr, "Error in pthread_mutex_unlock: %s\n", strerror(rc)); return rc; } /* Success! */ return 0; } /*------ end of EnqueueWork() ----------------*/ /* * NAME: DequeueWork() * * Dequeue the next work element from the queue, or if the queue * is empty, wait for more work to become available. * */ QueueElement *DequeueWork(void) { int rc; QueueElement *qP; /* Acquire mutex protecting the work queue. */ rc = pthread_mutex_lock(&QueueMutex); if (rc != 0) { fprintf(stderr, "Error in pthread_mutex_lock: %s\n", strerror(rc)); return NULL; } /* one less thread working */ NWorkersRunning -= 1; /* Loop waiting for work to become available. */ while ((WorkQueueP == NULL) && (NWorkersRunning > 0)) { NWorkersWaiting += 1; /* one more thread waiting */ rc = pthread_cond_wait(&QueueCond, &QueueMutex); if (rc != 0) { fprintf(stderr, "Error in pthread_cond_wait: %s\n", strerror(rc)); return NULL; } } /* Check whether we are done. */ if (WorkQueueP == NULL) { qP = NULL; /* Nothing more to do. */ /* signal any waiting threads */ if (NWorkersWaiting > 0) { NWorkersWaiting = 0; rc = pthread_cond_broadcast(&QueueCond); if (rc != 0) { fprintf(stderr, "Error in pthread_cond_broadcast: %s\n", strerror(rc)); } } } else { /* Remove the first element from the queue. */ qP = WorkQueueP; WorkQueueP = qP->nextP; qP->nextP = NULL; /* one more thread working */ NWorkersRunning += 1; } /* Release mutex before returning. */ rc = pthread_mutex_unlock(&QueueMutex); if (rc != 0) { fprintf(stderr, "Error in pthread_mutex_unlock: %s\n", strerror(rc)); return NULL; } return qP; } /*------ end of DequeueWork() ----------------*/ /* Main body for each thread. Loop until there is no more work to do. */ void* ThreadBody(void *parmP) { int rc; QueueElement *qP; /* Count this thread as a worker thread. */ NWorkersRunning += 1; /* Loop while there is work to do. */ do { /* Wait for work. */ qP = DequeueWork(); if (qP == NULL) break; /* Read this directory. */ rc = read_dir(qP->path, qP->ino); /* Free the work queue element. */ free(qP); } while (rc == 0); return 0; } /*------ end of ThreadBody() ----------------*/ /* * NAME: read_dir() * * Read all entries from the current directory. * */ int read_dir(const char *pathP, ino_t inode) { char fileName[MAX_FILE_NAME]; const gpfs_direntx_t *direntxP; gpfs_fssnap_handle_t *fsP = NULL; gpfs_ifile_t *dirxP = NULL; int rc = 0; /* Get the handle for the snapshot. */ fsP = gpfs_get_fssnaphandle_by_path(RootFsDirP); if (fsP == NULL) { rc = errno; fprintf(stderr, "gpfs_get_fssnaphandle_by_path(%s/%s): %s\n", RootFsDirP, pathP, strerror(rc)); goto exit; } /* Open the directory's file. */ dirxP = gpfs_iopen(fsP, inode, O_RDONLY, NULL, NULL); if (dirxP == NULL) { rc = errno; fprintf(stderr, "gpfs_iopen(%s/%s): %s\n", RootFsDirP, pathP, strerror(rc)); goto exit; } /* Loop reading the directory entries. */ while (1) { rc = gpfs_ireaddir(dirxP, &direntxP); if (rc != 0) { int saveerrno = errno; fprintf(stderr, "gpfs_ireaddir(%s/%s): rc %d %s\n", RootFsDirP, pathP, rc, strerror(saveerrno)); rc = saveerrno; goto exit; } if (direntxP == NULL) { break; } /* Check whether this entry is for a directory. */ if (direntxP->d_type == GPFS_DE_DIR) { if (strcmp(direntxP->d_name, "..") != 0) /* skip parent directory */ { if ((strcmp(direntxP->d_name, ".") == 0)) /* handle self directory */ { /* The entry is for the directory itself. Check the bit map array entry to determine whether the inode must be backed up. */ if (testBit(inodeBitsP, (UInt32) direntxP->d_ino) != 0) { /* Is the directory name too long? */ if (strlen(pathP)+2 > sizeof(fileName)) { fprintf(stderr, "read_dir: name %s too long\n", pathP); } else { /* Call routine to write a record for this inode to the shadow file. */ writeToShadowFile(pathP, direntxP->d_ino, 'Y'); } } } else /* handle sub-directory */ { /* Add this sub-directory to the work queue. */ rc = EnqueueWork(pathP, direntxP->d_name, direntxP->d_ino); if (rc != 0) { break; } } } } else /* The entry is for a file. */ { /* Check the bit map array entry to determine whether the inode must be backed up. */ if (testBit(inodeBitsP, (UInt32) direntxP->d_ino) != 0) { snprintf(fileName, MAX_FILE_NAME, "%s/%s", pathP, direntxP->d_name); if (strlen(fileName)+2 > sizeof(fileName)) { fprintf(stderr, "dirInodeWalk: name %s too long\n", fileName); } else { /* Write a record for this inode to the shadow file. */ writeToShadowFile(fileName, direntxP->d_ino, 'N'); } } } } exit: /* Close the open file, if necessary. */ if (dirxP) { gpfs_iclose(dirxP); } /* Free the snapshot handle. */ if (fsP) { gpfs_free_fssnaphandle(fsP); } return rc; } /*------ end of read_dir() ----------------*/ /* * NAME: writeToShadowFile() * * Create a record for the shadow file using the passed parameters, * obtain the mutex lock for the shadow file, write the record * to the file, and release the mutex lock. * * Care is taken to not introduce nulls into the shadow record. * This is done by using sprintf/memcpy instead of sprintf alone. */ int writeToShadowFile(const char *pathP, ino_t inode, const char inodeFlag) { char name[MAX_FILE_NAME]; char filenameLength[MAX_INT32_CHARS]; Int32 nameLength; char inode_num[MAX_INT32_CHARS]; backup_shadow_record_t shadowRecord; Int32 bytesWritten; int rc = RC_SUCCESS; /* Build the record to be written to the shadow file. */ /* Set the shadow record to blanks. */ memset((void *) &shadowRecord, ' ', sizeof(backup_shadow_record_t)); /* Copy the full path name into the transaction record. The name is enclosed with quotes because Tivoli requires this for filenames that contain an embedded blank. Rather than check each filename, we simply always use quotes. */ sprintf(name, "%s%s%s", "\"", pathP, "\""); nameLength = strlen(name); memcpy((void *) &shadowRecord.tr.filename, (const void *) &name, nameLength); /* Save the length of the filename for later use. This is done to avoid putting null characters in the shadow file because the sort command does not handle null characters well. */ sprintf(filenameLength, "%d", nameLength); memcpy((void *) &shadowRecord.tr.fnLength, (const void *) &filenameLength, strlen(filenameLength)); /* Copy the inode number as a string to the shadow record. */ sprintf(inode_num, "%d", inode); memcpy((void *) &shadowRecord.inodenum, (const void *) &inode_num, strlen(inode_num)); /* Set the directory flag and set the newline character. */ shadowRecord.inodeDir = inodeFlag; shadowRecord.newline_char = '\n'; /* Serialize the output with all the other threads. */ rc = pthread_mutex_lock(&OutputMutex); if (rc != 0) { fprintf(stderr, "Error in pthread_mutex_lock: %s\n", strerror(rc)); goto exit; } /* Write the shadow record to the file. */ bytesWritten = write(shadowFileHandle[0], &shadowRecord, sizeof(backup_shadow_record_t)); /* Increment the record count. */ shadowFileNumberOfRecords[0]++; /* Release the lock. */ rc = pthread_mutex_unlock(&OutputMutex); if (rc != 0) { fprintf(stderr, "Error in pthread_mutex_unlock: %s\n", strerror(rc)); goto exit; } /* Issue an error if the write failed. */ // BCH - should we assert or exit with a failure? if (bytesWritten != sizeof(backup_shadow_record_t)) { fprintf(stderr, "Error writing to %d; wrote %d bytes, not %d\n", shadowFileHandle[0], bytesWritten, sizeof(backup_shadow_record_t)); } exit: return rc; } /*------ end of writeToShadowFile() ----------------*/ /* * NAME: dirInodeWalk() * * Thread-based inode directory walker. */ int dirInodeWalk(const char *pathname, inodeBitsArray bit_map) { char fsSnapshotDir[MAX_FILE_NAME]; int i, rc, filearg; struct stat statBuf; pthread_t pThread; /* Set the root filesystem directory pointer. */ RootFsDirP = (char *) pathname; /* Initialize pthread variables. */ rc = pthread_mutex_init(&QueueMutex, NULL); if (rc != 0) { fprintf(stderr, "Error from pthread_mutex_init: %s\n", strerror(rc)); exit(rc); } rc = pthread_cond_init(&QueueCond, NULL); if (rc != 0) { fprintf(stderr, "Error from pthread_cond_init: %s\n", strerror(rc)); exit(rc); } rc = pthread_mutex_init(&OutputMutex, NULL); if (rc != 0) { fprintf(stderr, "Error from pthread_mutex_init: %s\n", strerror(rc)); exit(rc); } /* Get the inode number of the root directory. */ rc = stat(RootFsDirP, &statBuf); if (rc) { rc = errno; perror(RootFsDirP); return rc; } /* Verify that this is a directory; return if it is not. */ if (!S_ISDIR(statBuf.st_mode)) { errno = ENOTDIR; perror(RootFsDirP); return ENOTDIR; } /* Add the root directory to the work queue. */ snprintf(fsSnapshotDir, MAX_FILE_NAME, "%s/%s", fsName, snapshotSubdir); rc = EnqueueWork(fsSnapshotDir, fsSnapshotName, statBuf.st_ino); if (rc != 0) { return rc; } /* Start the requested number of threads. */ for (i = 1; i < nThreads; i++) { rc = pthread_create(&pThread, NULL, ThreadBody, NULL); if (rc != 0) { fprintf(stderr, "Error from pthread_create(%d): %s\n", i, strerror(rc)); exit(rc); } } /* This thread can do work, too. */ (void) ThreadBody(NULL); return rc; } /*------ end of dirInodeWalk() ----------------*/ /* * NAME: createSnapshotShadows() * * FUNCTION: * Construct shadow files of a snapshot and store them at the * mount point of the filesystem. * * PARAMETERS: * fsName: (IN) the name of the filesystem * numberOfShadowFiles (IN) the number of files to be created * bit_map (IN) points to a bit map array to be consulted * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: */ int createSnapshotShadows(char *fsName, Int32 numberOfShadowFiles, inodeBitsArray bit_map) { Int32 rc = RC_SUCCESS; Int32 i, handle; Int32 numShadows; Int32 numInodes; char filenameP[18]; char suffix[2]; Int32 *shadow_files[MAX_BACKUP_CLIENTS]; const gpfs_direntx_t *source = NULL; struct stat statBuf; char* fn = "createSnapshotShadows"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); numShadows = numberOfShadowFiles; /* Create shadow files. */ for (i = 0; i < numShadows; i++) { /* Create file name. */ memset(suffix, ' ', 2); sprintf(suffix, "%d", i); strcpy(filenameP, backupShadowName); strcat(filenameP, (const char *) suffix); snprintf(shadowFile[i], MAX_FILE_NAME, "%s/%s", fsName, filenameP); /* Create file in append mode. */ handle = open(shadowFile[i], O_CREAT | O_APPEND | O_RDWR | O_TRUNC, 0644); if (handle == -1) { fprintf(stderr, "Cannot create %s\n", filenameP); return(RC_FAIL); } else { shadowFileHandle[i] = handle; } } /* Stat the snapshot directory to get its inode_number. */ if (stat(fsSnapshotPathname, &statBuf) == -1) { fprintf(stderr, "Could not stat file '%s', errno=%d\n", fsSnapshotPathname, errno); return(RC_FAIL); } /* Update the newly-created backup shadow files. */ dirInodeWalk(fsSnapshotPathname, bit_map); /* Close the generated files. */ for (i = 0; i < numShadows; i++) { rc = close(shadowFileHandle[i]); if (rc == -1) { fprintf(stderr, "Cannot close file descriptor %d\n", shadowFileHandle[i]); return(RC_FAIL); } } if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of createSnapshotShadows() ----------------*/ /* * NAME: sortShadowfilesByInode() * * FUNCTION: * Sort the shadowFile[MAX_BACKUP_CLIENTS] files by inode number. * * PARAMETERS: * numShadows: (IN) the number of shadow files * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: * The number of shadowFile files is MAX_BACKUP_CLIENTS * for the time being. This may change if needed. */ int sortShadowfilesByInode(Int32 numShadows) { Int32 rc = RC_SUCCESS; Int32 i; char* fn = "sortShadowfilesByInode"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); for (i = 0; i < numShadows; i++) { /* Issue a sort command that will perform a numeric sort using the first field (the file's inode number) of each record. */ #ifdef GPFS_AIX sprintf(sstring, "/usr/bin/sort -b -k 1,1n -o %s %s > /dev/null", shadowFile[i], shadowFile[i]); #else sprintf(sstring, "/bin/sort -b -k 1,1n -o %s %s > /dev/null", shadowFile[i], shadowFile[i]); #endif rc = system(sstring); /* Check system() return. */ if (rc != 0) { fprintf(stderr, "system(%s) returned %d\n", sstring, rc); return(RC_FAIL); } } if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of sortShadowfilesByInode() ----------------*/ /* * NAME: sortShadowfilesByFilename() * * FUNCTION: * Sort the shadowFile[0] file into alphabetical order by filename * and store the results in a single file, backupShadowFile. * * PARAMETERS: * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * */ int sortShadowfilesByFilename() { Int32 rc = RC_SUCCESS; Int32 i; char* fn = "sortShadowfilesByFilename"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Issue a sort command that will perform an alphabetical sort on the second field (the filename of each file). */ #ifdef GPFS_AIX sprintf(sstring, "/usr/bin/sort -b -k 2,2 -o %s %s > /dev/null", backupShadowFile, shadowFile[0]); #else sprintf(sstring, "/bin/sort -b -k 2,2 -o %s %s > /dev/null", backupShadowFile, shadowFile[0]); #endif rc = system(sstring); /* Check system() return. */ if (rc != 0) { fprintf(stderr, "system(%s) returned %d\n", sstring, rc); return(RC_FAIL); } #ifdef DEBUG_BACKUP /* Leave the shadow files so that they may get examined. */ #else /* Delete the shadowFile[0] file. */ unlink(shadowFile[0]); #endif /* DEBUG_BACKUP */ if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of sortShadowfilesByFilename() ----------------*/ /* * NAME: updateShadowfilesFilesizes() * * FUNCTION: * Update the size field in the shadowFile[MAX_BACKUP_CLIENTS] files * from the pertinent information in the file sizes file. * * PARAMETERS: * numShadows: (IN) The number of shadow files * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: * The number of shadowFile files is MAX_BACKUP_CLIENTS for the * time being. This may change if needed. */ int updateShadowfilesFilesizes(Int32 numShadows) { Int32 offset, inodeDelta, rc1, rc = RC_SUCCESS; Int32 shadowRecordInode, filesizesRecordInode; Int32 fileIndex, bytesRead, bytesWritten; char* eq_p; Int64 recordsLeft, recordInFilesizes, numberOfFilesizesRecords; Int64 recordInShadowFile; offset_t nextWriteOffset, nextReadOffset, actualOffset; backup_shadow_record_t shadowRecord; backup_filesizes_t filesizesRecord; struct stat statBuf; Boolean readNewRecord = true; size_t shadowRecordSize = sizeof(backup_shadow_record_t); size_t filesizesRecordSize = sizeof(backup_filesizes_t); char* fn = "updateShadowfilesFilesizes"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Open the filesizes file. */ filesizesHandle = open(filesizesFile, O_RDONLY, 0664); if (filesizesHandle == -1) { fprintf(stderr, "Cannot open %s\n", filesizesFile); return(RC_FAIL); } /* Stat the filesizes file. */ if (stat(filesizesFile, &statBuf) == -1) { fprintf(stderr, "Could not stat file '%s', errno=%d\n", filesizesFile, errno); return(RC_FAIL); } /* Determine the number of records in the filesizes file. */ numberOfFilesizesRecords = statBuf.st_size / filesizesRecordSize; /* Point to the first record in the filesizes file. */ recordInFilesizes = 0; actualOffset = lseek(filesizesHandle, recordInFilesizes, SEEK_SET); if (actualOffset != 0) { fprintf(stderr, "Cannot seek to %lld in %s\n", recordInFilesizes, filesizesFile); return(RC_FAIL); } /* Open all the shadowFile[] files. */ for (fileIndex = 0; fileIndex < numShadows; fileIndex++) { shadowFileHandle[fileIndex] = open(shadowFile[fileIndex], O_RDWR, 0664); if (shadowFileHandle[fileIndex] == -1) { fprintf(stderr, "Cannot open %s\n", shadowFile[fileIndex]); return(RC_FAIL); } } /* Update the filesize field in the records of the backup shadow files. */ for (fileIndex = 0; fileIndex < numShadows; fileIndex++) { recordInShadowFile = 0; nextReadOffset = recordInShadowFile*shadowRecordSize; /* Position the file pointer for the current shadow file. */ actualOffset = lseek(shadowFileHandle[fileIndex], recordInShadowFile*shadowRecordSize, SEEK_SET); if (actualOffset != nextReadOffset) { fprintf(stderr, "Cannot seek to %lld in %s\n", recordInShadowFile*shadowRecordSize, shadowFile[fileIndex]); return(RC_FAIL); } nextWriteOffset = nextReadOffset; recordsLeft = shadowFileNumberOfRecords[fileIndex]; while (recordsLeft > 0) { /* Read the next record from the shadow file. */ bytesRead = read(shadowFileHandle[fileIndex], &shadowRecord, shadowRecordSize); if (bytesRead != shadowRecordSize) { fprintf(stderr, "Error reading file %s\n", shadowFile[fileIndex]); return(RC_FAIL); } /* Calculate the next offset and the number of remaining records. */ nextReadOffset += shadowRecordSize; recordsLeft--; /* Search the filesizes file for the inode that matches the shadow record's inode. */ while (recordInFilesizes < numberOfFilesizesRecords) { /* If warranted, read a new record from the filesizes file. */ if (readNewRecord) { bytesRead = read(filesizesHandle, &filesizesRecord, filesizesRecordSize); if (bytesRead != filesizesRecordSize) { fprintf(stderr, "Error reading from file descriptor %d\n", filesizesHandle); return(RC_FAIL); } } #if 0 // Historical note: // We were failing the test below in the incremental case // because there are more records in the filesizes file than in // the shadow file - the extra records were the unchanged files, // and these had a filesize of 0. What should be done? // This problem did not happen in the past because filesizes // were not considered by createClientFilelists, and therefore // this routine (updateShadowfilesFilesizes()) was not called // in the incremental case. One possible solution would be to // use createClientFilelists2 (the divy-by-numbers-of-files // version) rather than createClientFilelists for incremental. // In this case we must avoid calling updateShadowfilesFilesizes(). // A 2nd solution would be to change updateSnapshotFilesizes() // to skip over filesizes records for which the filesize is 0. // I chose the 1st solution (don't worry about filesizes // for incremental backups) for now. (BCH) // // BCH - The above comment may no longer be needed. We are calling // BCH updateShadowfilesFilesizes() in the incremental case // BCH without trouble now. Should we consider going back to using // BCH createClientFilelists() instead of createClientFilelists2() // BCH in the incremental case again? // // BCH - The code has moved on once again, with the test below // BCH no longer being used, and the code allowing for the // BCH shadow file and the filesizes file to have different numbers // BCH of records (this was needed to support hard links, which // BCH results in one record in the filesizes file, but two // BCH records in the shadow file, one for the file itself, // BCH and one for the hardlink). The net of all this is that // BCH someday we may wish to go back to using the same routine // BCH (createClientFilelists) for incremental backups that // BCH we now use for full backups. /* If the inode numbers from the shadow file record and the filesizes record match, copy the filesize to the shadow file. */ if ((gpfs_ino_t) atoi((const char *) shadowRecord.inodenum) == (gpfs_ino_t) atoi((const char *) filesizesRecord.inodenum)) #endif /* Compare the inodes from the shadow file and the filesizes file. */ shadowRecordInode = atoi((const char *) shadowRecord.inodenum); filesizesRecordInode = atoi((const char *) filesizesRecord.inodenum); inodeDelta = shadowRecordInode - filesizesRecordInode; if (inodeDelta > 0) { /* We have not yet encountered a file in the filesizes file that comes lexicographically after the file from the shadow file, so update the record number and read again. */ recordInFilesizes++; readNewRecord = true; // Read a new filesizes record. continue; } if (inodeDelta == 0) { /* Since the inode numbers from the shadow file record and the filesizes record match, copy the filesize to the shadow file. */ memcpy((void *) &shadowRecord.tr.filesize, (const void *) &filesizesRecord.filesize, sizeof(filesizesRecord.filesize)); nextWriteOffset = nextReadOffset - shadowRecordSize; /* Reposition the file pointer for the shadow file. */ actualOffset = lseek(shadowFileHandle[fileIndex], nextWriteOffset, SEEK_SET); if (actualOffset != nextWriteOffset) { fprintf(stderr, "Could not seek to offset L%d in file %s\n", nextWriteOffset, shadowFile[fileIndex]); return(RC_FAIL); } /* Write the shadow record back to the shadow file. */ bytesWritten = write(shadowFileHandle[fileIndex], &shadowRecord, shadowRecordSize); if (bytesWritten != shadowRecordSize) { fprintf(stderr, "Error writing file %s\n", shadowFile[fileIndex]); return(RC_FAIL); } /* Cause the next record in the shadow file to be read. */ recordInShadowFile++; readNewRecord = false; // Don't read a new filesizes record yet. break; } if (inodeDelta < 0) { /* We just encountered an inode in the filesizes file that comes lexicographically after the inode from the shadow file, so the inode does not exist in the filesizes file. This should never happen, so issue an error message and quit. */ fprintf(stderr, "Inode number %s in file %s could not be found in file %s\n", shadowRecord.inodenum, shadowFile[fileIndex], filesizesFile); return(RC_FAIL); } } /* end of while (recordInFilesizes < numberOfFilesizesRecords) */ } /* end of while (recordsLeft > 0) */ } /* end for (i = 0; i < numShadows; i++) */ #ifdef DEBUG_BACKUP /* Leave the filesizes file so that it may get examined. */ #else /* Delete the filesizes file. */ unlink(filesizesFile); #endif /* DEBUG_BACKUP */ if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of updateShadowfilesFilesizes() ----------------*/ /* * NAME: createFilelist() * * FUNCTION: * Create the transactions file. Update it to include the full * path names of the files to be processed (i.e., get backed_up, * get restored, etc.) by the server. * * PARAMETERS: * fsName: (IN) Points to the filesystem name. * backupControlHdrP: (IN) Pointer to a gpfs_backup_control_t structure * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: */ int createFilelist(char *fsName, gpfs_backup_control_t *backupControlHdrP) { gpfs_backup_control_t *backupCtrlP; backup_shadow_record_t backupShadowRecord; Int64 currentServerFilesSize = 0; Int64 sizeOfFilesInBuffer = 0; Int64 numberOfInodes; Int64 recordInFile; Int32 i, rc = RC_SUCCESS; Int32 handle; Int32 backupShadowHandle; Int32 bufferIndex; Int32 bytesRead; transactionsListRecord buffer[NRECS_PER_BUFFER]; offset_t nextReadOffset, actualOffset; size_t shadowRecordSize; size_t transactionRecordSize; char* fn = "createFilelist"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Initializations */ shadowRecordSize = sizeof(backup_shadow_record_t); transactionRecordSize = sizeof(transactionsListRecord); backupCtrlP = backupControlHdrP; numberOfInodes = backupCtrlP->backupHdr.inodesTotal; /* Create the transaction file in append mode. */ transactionsListHandle = open(transactionsList, O_CREAT | O_APPEND | O_WRONLY | O_TRUNC, 0644); if (transactionsListHandle == -1) { fprintf(stderr, "Cannot create %s\n", transactionsList); return(RC_FAIL); } /* Open the backup shadow file in READ mode. */ backupShadowHandle = open(backupShadowFile, O_RDONLY, 0644); if (backupShadowHandle == -1) { fprintf(stderr, "Cannot open %s\n", backupShadowFile); return(RC_FAIL); } /* Seek the backup shadow file's first record. */ recordInFile = 0; nextReadOffset = recordInFile*shadowRecordSize; actualOffset = lseek(backupShadowHandle, recordInFile*shadowRecordSize, SEEK_SET); if (actualOffset != nextReadOffset) { fprintf(stderr, "Cannot seek to %lld in %s\n", recordInFile*shadowRecordSize, backupShadowFile); return(RC_FAIL); } /* * Do the following: * - Read a record from the backup shadow file. * The first record read should be for a directory. * - Write the record to the buffer. * - If the buffer is full, write the buffer to the transactions file. * - Repeat these steps while there are records in the file. */ bytesRead = read(backupShadowHandle, &backupShadowRecord, shadowRecordSize); if (bytesRead != shadowRecordSize) { fprintf(stderr, "Error reading file %s\n", backupShadowFile); return(RC_FAIL); } /* The first record should be for the snapshot directory which is located at the mount point of the filesystem. Assert if it is not. */ // BCH - Should we do something better here than just assert? // BCH At the very least I think we should put out an error message first. assert(strchr((const char *) &(backupShadowRecord.inodeDir), 'Y') != NULL); /* Copy the backup shadow record's transaction record to the buffer. */ bufferIndex = 0; buffer[bufferIndex] = backupShadowRecord.tr; buffer[bufferIndex].delimeter2 = '\n'; /* Increment the aggregate size of the files in the buffer. */ sizeOfFilesInBuffer += strtoll((const char *) backupShadowRecord.tr.filesize, NULL, 10); /* Copy the 1st filename into the anchor name for the server. */ strncpy(backupCtrlP->inode.filename, buffer[bufferIndex].filename, MAX_FILE_NAME); /* Decrement the number of inodes now that the first has been processed. */ numberOfInodes--; /* Loop until the remaining inodes have been processed. */ while (numberOfInodes > 0) { /* Read the next record from the backup shadow file. */ bytesRead = read(backupShadowHandle, &backupShadowRecord, shadowRecordSize); if (bytesRead != shadowRecordSize) { fprintf(stderr, "Error reading file %s\n", backupShadowFile); return(RC_FAIL); } /* Decrement the number of inodes and point to the next buffer record. */ numberOfInodes--; bufferIndex++; /* Copy the backup shadow record filename to the buffer. */ buffer[bufferIndex] = backupShadowRecord.tr; buffer[bufferIndex].delimeter2 = '\n'; /* Increment the aggregate size of the files in the buffer. */ sizeOfFilesInBuffer += strtoll((const char *) backupShadowRecord.tr.filesize, NULL, 10); /* Write the buffer to the transactions file if the buffer is full. */ if ((bufferIndex + 1) == NRECS_PER_BUFFER) { /* Write the buffer to the file. */ rc = writeBuffer(transactionsListHandle, buffer, (bufferIndex + 1) * transactionRecordSize); if (rc != 0) { fprintf(stderr, "createFilelist(): writeBuffer() returned rc = %d\n", rc); return(RC_FAIL); } /* Update the size value. */ currentServerFilesSize += sizeOfFilesInBuffer; /* Clear the buffer for reuse. */ sizeOfFilesInBuffer = 0; bufferIndex = -1; memset(buffer, 0, transactionRecordSize*NRECS_PER_BUFFER); } } /* end while (numberOfInodes > 0) */ /* Write the last buffer to the transactions file. */ rc = writeBuffer(transactionsListHandle, buffer, (bufferIndex + 1) * transactionRecordSize); if (rc != 0) { fprintf(stderr, "createFilelist(): writeBuffer() returned rc = %d\n", rc); return(RC_FAIL); } /* Update the aggregate size for the server. */ currentServerFilesSize += sizeOfFilesInBuffer; backupCtrlP->inode.serverFilesSize = currentServerFilesSize; /* Close the created transactions file. */ rc = close(transactionsListHandle); if (rc != 0) { fprintf(stderr, "Cannot close file descriptor transactionsListHandle = %d\n", transactionsListHandle); return(RC_FAIL); } if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of createFilelist() ----------------*/ /* * NAME: processFilelist() * * FUNCTION * Given a transactions file containing a list of files to be * processed on the backup server, a list of backup clients to do * the backup, and a TSM command to run, do the following: * a) For each client node process, construct the corresponding * filelist file. * b) For each client node process, tell the client node to execute * the TSM command against its corresponding filelist file. * c) Process the return from each client node process regarding * the level of success from the command execution. * * PARAMETERS: * fsName: (IN) the full path name for the filesystem * transactionCmd: (IN) the command the client node will execute * against a filelist file * transactionCmdOptn: (IN) an option value to be passed to the * transactionCmd * backupControlHdrP: (IN) pointer to a gpfs_backup_control_t structure * divyBySize: (IN) boolean flag indicating whether to divy the * the filelist based on file sizes or not * * RETURNS: * RC_SUCCESS on success, RC_PSUCCESS on partial success, RC_FAIL on error. * * NOTES: */ int processFilelist(char *fsName, char *transactionCmd, char *transactionCmdOptn, gpfs_backup_control_t *backupControlHdrP, Boolean divyBySize) { Int32 tmp, rc = RC_SUCCESS; Int32 clientIndex, processIndex; Int32 numberOfClients, numberOfProcesses; static char *processCommand = "execTSMcommand"; // mmremote verb FILE* filePtr[MAX_BACKUP_CLIENT_PROCESSES]; char *cmdReturnString[MAX_BACKUP_CLIENT_PROCESSES]; char *returnStrings; backup_client_info_t *backupClient[MAX_BACKUP_CLIENTS]; pid_t pid[MAX_BACKUP_CLIENTS]; char processCmdBuffer[MAX_BACKUP_CLIENT_PROCESSES][LINE_LENGTH]; char linebuf[LINE_LENGTH]; /* buffer to store an output line */ gpfs_backup_control_t *backupCtrlP; Boolean pendingTransactions = false; Boolean partialSuccess = false; char* fn = "processFilelist"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* initializations */ backupCtrlP = backupControlHdrP; numberOfClients = backupCtrlP->backupHdr.icf.numberOfClients; /* Create the transaction files for the client processes. */ if (divyBySize) { rc = createClientFilelists(backupCtrlP); } else { rc = createClientFilelists2(backupCtrlP); } /* Delete the original transactions file now that we are done with it. */ unlink(transactionsList); /* If the call to createClientFilelists() or createClientFilelists2() failed, print an error message and return with a bad return code. */ if (rc == RC_FAIL) { fprintf(stderr, "processFilelist(): createClientFilelists() returned %d\n", rc); return(RC_FAIL); } /* Get the number of processes that we will be creating. */ numberOfProcesses = backupCtrlP->backupHdr.numberOfProcesses; /* Get the client nodes and storage for their command strings. */ returnStrings = (char *) calloc(numberOfProcesses, (LINE_LENGTH)); /* Report error if calloc failed. */ if (returnStrings == NULL) { fprintf(stderr, "processFilelist(): calloc failure"); return(RC_FAIL); } /* We depend on unused areas of the returnStrings being null chars. */ memset(returnStrings, '\0', (numberOfProcesses * (LINE_LENGTH))); /* Initialize cmdReturnString array with pointers into returnStrings. */ for (processIndex = 0; processIndex < numberOfProcesses; processIndex++) { cmdReturnString[processIndex] = returnStrings+(processIndex*(LINE_LENGTH)); } /* Initialize the backupClient array. */ for (clientIndex = 0; clientIndex < numberOfClients; clientIndex++) { backupClient[clientIndex] = &(backupCtrlP->backupHdr.icf.clients[clientIndex]); } /* Create the pending transactions filename strings based on whether we are processing deletions or changes. */ if (strcmp(transactionCmd, "expire") == 0) { strcpy(pendingTransactionsListName, pendingDeletionsName); snprintf(pendingTransactionsList, MAX_FILE_NAME, "%s/%s", fsName, pendingDeletionsName); } else { strcpy(pendingTransactionsListName, pendingChangesName); snprintf(pendingTransactionsList, MAX_FILE_NAME, "%s/%s", fsName, pendingChangesName); } if (Tracing) traceLine("tsbackup.C", fn, __LINE__); /* * The following loop forks processes that do the backups in parallel * on the TSM clients. Each process invokes a script on a client node * which in turn issues the desired TSM backup command. Each client * may run multiple processes in parallel. */ for (processIndex = 0; processIndex < numberOfProcesses; processIndex++) { /* Calculate the client index from the process index value. */ clientIndex = processIndex % numberOfClients; /* Create the command to be forked. */ snprintf(processCmdBuffer[processIndex], sizeof(processCmdBuffer[processIndex]), "%s/bin/mmcommon on1long %s %s %s %s %s %s %s %d %d %s %d", INSTALL_DIRECTORY, backupClient[clientIndex]->clientName, processCommand, fsName, transactionCmd, transactionCmdOptn, clientTransactionsList[processIndex], masterNode, masterPID, processIndex, backupCtrlP->backupHdr.icf.serverName, ioRate); /* Fork a command to cause the backup to execute as a client process. */ filePtr[processIndex] = pipeOpen(processCmdBuffer[processIndex], "r", &pid[processIndex]); if (filePtr[processIndex] == NULL) { /* * The client process failed. Rename its transactions file * to serve as a pending transactions file and set a flag * to indicate that there are pending transactions. */ /* Create the pending transactions file name. */ snprintf(clientPendingTransactionsList[processIndex], MAX_FILE_NAME, "%s/%s%d", fsName, pendingTransactionsListName, processIndex); /* Rename the process's transactions file to a pending trans file. */ rc = rename(clientTransactionsList[processIndex], clientPendingTransactionsList[processIndex]); if (rc != 0) { fprintf(stderr, "rename(%s, %s) returned %d\n", clientTransactionsList[processIndex], clientPendingTransactionsList[processIndex], rc); free(returnStrings); return(RC_FAIL); } /* Set the pending transactions flag and issue a message. */ pendingTransactions = true; fprintf(stderr, "processFilelist(): pipeOpen() returned filePtr[%d] = NULL\n", processIndex); rc = RC_FAIL; // BCH - I believe this may be superfluous } } if (Tracing) traceLine("tsbackup.C", fn, __LINE__); /* Loop through the client processes to get each one's return string. */ /* NOTE: When at least one process has at least partially successfully backed up its list of files, the overall operation will be indicated as having had at least a partial successful completion. */ for (processIndex = 0; processIndex < numberOfProcesses; processIndex++) { if (fgets(linebuf, sizeof(linebuf), filePtr[processIndex]) != NULL) { strcpy(cmdReturnString[processIndex], linebuf); } else { /* Handle the case where the client process has failed. */ /* First create the pending transactions file name. */ snprintf(clientPendingTransactionsList[processIndex], MAX_FILE_NAME, "%s/%s%d", fsName, pendingTransactionsListName, processIndex); /* Rename the process's transactions file to a pending trans file. */ rc = rename(clientTransactionsList[processIndex], clientPendingTransactionsList[processIndex]); if (rc != 0) { fprintf(stderr, "rename(%s, %s) returned %d\n", clientTransactionsList[processIndex], clientPendingTransactionsList[processIndex], rc); free(returnStrings); return(RC_FAIL); } fprintf(stderr, "processFilelist(): no output received from client process %d\n", processIndex); pendingTransactions = true; rc = RC_FAIL; // BCH - I believe this may be superfluous } /* Close the pipe that was opened for the client process. */ pipeClose(filePtr[processIndex], pid[processIndex]); } if (Tracing) traceLine("tsbackup.C", fn, __LINE__); /* * Check the completion level to determine * the level of success for the operation. */ for (processIndex = 0; processIndex < numberOfProcesses; processIndex++) { /* Calculate the client index from the process index value. */ clientIndex = processIndex % numberOfClients; if (strstr(cmdReturnString[processIndex], "Operation_Success") != NULL) { /* In this case all the files were successfully backed up. Set a flag indicating at least partial successful operation completion. */ partialSuccess = true; if (Tracing) { fprintf(stderr, "Process %d on client %s successfully processed its list of files.\n", processIndex, backupClient[clientIndex]->clientName); } #ifdef DEBUG_BACKUP /* Do not delete the clientTransactionsList[processIndex] file. */ #else /* Delete the clientTransactionsList[processIndex] file. */ unlink(clientTransactionsList[processIndex]); #endif /* DEBUG_BACKUP */ } else if (strstr(cmdReturnString[processIndex], "Operation_Partial_Success") != NULL) { /* Not all of the files were successfully backed up. The mmexectsmcmd script should have created a file named PendingTransactions_L (where L is the process index) which contains the names of the files that were not successfully operated on. Save the name of this file into clientPendingTransactionsList, the pending transaction files array. */ /* Set flag indicating at least partial successful operation. */ partialSuccess = true; pendingTransactions = true; fprintf(stderr, "Process %d on client %s had partial success in processing its list of files.\n", processIndex, backupClient[clientIndex]->clientName); /* Save the name of the pending transactions file into the clientPendingTransactionsList array. */ snprintf(clientPendingTransactionsList[processIndex], MAX_FILE_NAME, "%s/%s%d", fsName, pendingTransactionsListName, processIndex); #ifdef DEBUG_BACKUP /* Do not delete the clientTransactionsList[processIndex] file. */ #else /* Delete the clientTransactionsList[processIndex] file. */ unlink(clientTransactionsList[processIndex]); #endif /* DEBUG_BACKUP */ } else if ((strstr(cmdReturnString[processIndex],"Operation_Failure") != NULL) || (strstr(cmdReturnString[processIndex],"Already_running") != NULL)) { /* The backup operation on the client process failed. Rename its transactions file to clientPendingTransactionsList_L. */ /* Create the process's pending transactions file name. */ snprintf(clientPendingTransactionsList[processIndex], MAX_FILE_NAME, "%s/%s%d", fsName, pendingTransactionsListName, processIndex); /* Rename the process's transactions file to a pending trans file. */ rc = rename(clientTransactionsList[processIndex], clientPendingTransactionsList[processIndex]); if (rc != 0) { fprintf(stderr, "rename(%s, %s) returned %d\n", clientTransactionsList[processIndex], clientPendingTransactionsList[processIndex], rc); free(returnStrings); return(RC_FAIL); } /* Issue a message to help the user understand what failed and set the transaction pending flag and a bad return code. */ fprintf(stderr, "\n%s\n", 2 + strstr(cmdReturnString[processIndex], ":")); fprintf(stderr, "Process %d on client %s failed in processing its list of files.\n", processIndex, backupClient[clientIndex]->clientName); pendingTransactions = true; rc = RC_FAIL; // BCH - I believe this may be superfluous } else { fprintf(stderr, "Process %d on client %s returned unexpected results: %s\n", processIndex, backupClient[clientIndex]->clientName, cmdReturnString[processIndex]); free(returnStrings); return(RC_FAIL); } } if (Tracing) traceLine("tsbackup.C", fn, __LINE__); /* Return the storage we obtained for the return strings. */ free(returnStrings); /* Set the return code and do required cleanup based on the outcome. */ if (partialSuccess) { if (pendingTransactions) /* partial success */ { /* Merge the pendingTransactionsList_L files into a single pendingTransactionsList file. */ rc = createPendingFile(fsName, backupCtrlP, pendingTransactionsList); if (rc != 0) { fprintf(stderr, "createPendingFile() failed with rc = %d\n", rc); return(RC_FAIL); } /* Set the return code. */ rc = RC_PSUCCESS; } else /* total success */ { /* Set the return code. */ rc = RC_SUCCESS; } } else /* total failure */ { /* Delete any existing client transactions files. */ for (processIndex = 0; processIndex < numberOfProcesses; processIndex++) { /* Delete the client transactions file and the client pending transactions file if either exists. */ unlink(clientTransactionsList[processIndex]); unlink(clientPendingTransactionsList[processIndex]); } /* Set the return code. */ rc = RC_FAIL; } // end if (partialSuccess) if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of processFilelist() ----------------*/ /* * NAME: createPendingFile() * * FUNCTION: * Consolidate the pending transactions files from the client processes * into a single pending transactions file relating to the server. * * PARAMETERS: * fsName: (IN) full path name of filesystem mount point * backupControlHdrP: (IN) pointer to a gpfs_backup_control_t structure * pendingFile: (IN) name of file to be created containing the * pending transactions * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: * It is assumed that if a client has failed to backup all the files which * were to be backed up by that client, a file exists associated with that * client which contains all the file names which were not backed up. */ int createPendingFile(char *fsName, gpfs_backup_control_t *backupControlHdrP, char *pendingFile) { Int32 numberOfClients, processesPerClient, numberOfProcesses; Int32 rc = RC_SUCCESS; Int32 clientIndex, j; char *filenamesString = NULL; gpfs_backup_control_t *backupCtrlP; struct stat statBuf; Boolean pendingFiles = false; char* fn = "createPendingFile"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Initialize some pointers. */ backupCtrlP = backupControlHdrP; /* Initialize the number of clients and the number of client processes. */ numberOfClients = backupCtrlP->backupHdr.icf.numberOfClients; processesPerClient = backupCtrlP->backupHdr.icf.processesPerClient; numberOfProcesses = numberOfClients * processesPerClient; /* If there is only one client process for the server and the clientPendingTransactionsList[0] file exists, just rename it to pendingTransactionsList file. */ if (numberOfProcesses == 1) { /* Check the existence of the clientPendingTransactionsList_0 file. */ rc = stat(clientPendingTransactionsList[0], &statBuf); if (rc == -1) { if (errno == ENOENT) { fprintf(stderr, "File %s does not exist.\n", clientPendingTransactionsList[0]); } else { fprintf(stderr, "Could not stat file '%s', errno=%d\n", clientPendingTransactionsList[0], errno); } return(RC_FAIL); } else { /* Rename the client process pending transactions file to the desired pending transactions file. */ rc = rename(clientPendingTransactionsList[0], pendingFile); if (rc != 0) { fprintf(stderr, "rename(%s, %s) returned %d\n", clientPendingTransactionsList[0], pendingTransactionsList, rc); return(RC_FAIL); } } } else /* We come here if there is more than one client process. */ { /* For each client process, determine whether a pending transactions file exists. If so, put its name into a string of client process pending transaction files. If the string contains one or more names, issue a cat command to have all the files concatenated into one pending transactions file for the server. */ for (j = 0; j < numberOfProcesses; j++) { rc = stat(clientPendingTransactionsList[j], &statBuf); if (rc == -1) { if (errno == ENOENT) { /* The clientPendingTransactionsList_j file does not exist. */ continue; } else { fprintf(stderr, "Could not stat file '%s', errno=%d\n", clientPendingTransactionsList[j], errno); return(RC_FAIL); } } else { /* Set a flag to indicate that there are pending files to process. */ pendingFiles = true; /* Get initial space for string or increase the space as necessary. */ if (filenamesString == NULL) { /* Obtain space for the name + 2 extra characters (' ' and '\0'). */ filenamesString = (char*) malloc( strlen((clientPendingTransactionsList[j]) + 2) * sizeof(char)); if (filenamesString == NULL) { fprintf(stderr, "createPendingFile(): malloc failure\n"); return(RC_FAIL); } /* Zero the storage for the string before using it. */ memset(filenamesString, '\0', sizeof(char)); } else { char* oldNamesString; oldNamesString = filenamesString; /* Obtain additional space for the name plus 2 extra characters (' ' and '\0'). */ filenamesString = (char*) realloc(filenamesString, (strlen(filenamesString) + 2 + strlen(clientPendingTransactionsList[j])) * sizeof(char)); if (filenamesString == NULL) { /* free old memory */ free(oldNamesString); fprintf(stderr, "createPendingFile(): realloc failure\n"); return(RC_FAIL); } } /* Append the file name to a string consisting of the names of all client process pending transactions files separated by blanks. */ strcat(filenamesString, clientPendingTransactionsList[j]); strcat(filenamesString, " "); } } /* for (j = 0; j < numberOfProcesses; j++) */ } /* If there are pending files because one or more of the processes failed, concatenate the files together into a single pending transactions file. */ if (pendingFiles) { /* Create the server's pending transactions file name. */ snprintf(pendingTransactionsList, MAX_FILE_NAME, "%s/%s", fsName, pendingTransactionsListName); /* Concatenate the pending transations files from the client processes into a single file that is the server's pending transactions file. */ // BCH - Recode this to use a loop with open(), read(), write(), close() // BCH It will be much more efficient (faster reads and no system() ) #ifdef GPFS_AIX snprintf(sstring, MAX_COMMAND_STRING, "/usr/bin/cat %s > %s", filenamesString, pendingTransactionsList); #else snprintf(sstring, MAX_COMMAND_STRING, "/bin/cat %s > %s", filenamesString, pendingTransactionsList); #endif rc = system(sstring); /* Check system() return. */ if (rc != 0) { fprintf(stderr, "system(%s) returned %d\n", sstring, rc); return(RC_FAIL); } #ifdef DEBUG_BACKUP /* Do not remove the clientPendingTransactionsList files. */ #else /* Issue the rm command to have the clientPendingTransactionsList files deleted. Using rm instead of unlink allows us to delete them all in one command. */ // BCH - true, but you pay for the overhead of system(). Recode? snprintf(sstring, MAX_COMMAND_STRING, "%s %s > /dev/null", RM, filenamesString); rc = system(sstring); /* Check system() return. */ if (rc != 0) { fprintf(stderr, "system(%s) returned %d\n", sstring, rc); return(RC_FAIL); } #endif /* DEBUG_BACKUP */ /* Free the memory for the string of file names. */ free(filenamesString); filenamesString = NULL; } else { rc = RC_SUCCESS; } if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of createPendingFile() ----------------*/ /* * NAME: doFSFileDeletions() * * FUNCTION: * Determine the files which have been deleted from a filesystem * and expire them from the backed up copy of the filesystem. * * PARAMETERS: * fsName: (IN) The full path name of the mounted filesystem. * fsSnapshotPathname: (IN) The full path name of a directory name * indicating where a snapshot of the filesystem * is to be stored. * RETURNS: * RC_SUCCESS on success, RC_PSUCCESS on partial success, RC_FAIL on error. * * NOTES: */ int doFSFileDeletions(char *fsName, char *fsSnapshotPathname) { Int32 tmp, rc = RC_SUCCESS; Int32 numShadows; Boolean fileDeletions = false; inodeBitsArray *inodeBitsArrayP; char* fn = "doFSFileDeletions"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Do a regular inode scan. The inodes for all the files in the filesystem will be returned. */ inodeBitsArrayP = &inodeBitsP; rc = doInodeScan(fsSnapshotPathname, inodeBitsArrayP, backupControlP); if (rc != 0) { fprintf(stderr, "doFSFileDeletions(): doInodeScan() returned rc = %d\n", rc); return(RC_FAIL); } /* Create a backup shadow file, and store it at the mount point of the filesystem. */ numShadows = 1; rc = createSnapshotShadows(fsName, numShadows, inodeBitsP); if (rc != 0) { fprintf(stderr, "createSnapshotShadows() returned rc = %d\n", rc); free(inodeBitsP); return(RC_FAIL); } free(inodeBitsP); /* Sort the backup shadow file(s) by inode number. */ rc = sortShadowfilesByInode(numShadows); if (rc != RC_SUCCESS) { fprintf(stderr, "sortShadowfilesByInode() returned rc = %d\n", rc); return(RC_FAIL); } /* Update the .backup_shadow0 file with the correct file sizes and erase the filesizes file. */ rc = updateShadowfilesFilesizes(numShadows); if (rc != RC_SUCCESS) { fprintf(stderr, "updateShadowfilesFilesizes() returned rc = %d\n", rc); return(RC_FAIL); } /* Sort the backup shadow file into alphabetical order by filename, and store the results in a single backup shadow file. */ rc = sortShadowfilesByFilename(); if (rc != RC_SUCCESS) { fprintf(stderr, "sortShadowfilesByFilename() returned rc = %d\n", rc); return(RC_FAIL); } /* Get the list of files to be deleted. */ rc = getFilelistDeletions(backupShadowCheck, backupShadowFile, &fileDeletions, (char *) &deletionsFile); if (rc != 0) { fprintf(stderr, "getFilelistDeletions() returned rc = %d\n", rc); unlink(deletionsFile); return(RC_FAIL); } /* Take an early exit if there are no file deletions. */ if (fileDeletions == false) { return(RC_SUCCESS); } /* Rename the deletions file to be the transactions file. */ tmp = rename(deletionsFile, transactionsList); if (tmp != 0) { fprintf(stderr, "rename(%s, %s) returned %d\n", deletionsFile, transactionsList, rc); return(RC_FAIL); } /* Invoke routine to process the transactions file. The false flag that is passed tells the routine to divy the transactions file among the client processes by number of files, not by aggregate file size, which is what makes sense when doing deletions. */ rc = processFilelist(fsName, transactionCmdExpire, transactionCmdOption, backupControlP, false); /* Process the return code. */ switch (rc) { case RC_FAIL: /* failure */ fprintf(stderr, "processFilelist() returned rc = %d\n", rc); break; case RC_PSUCCESS: /* partial success */ case RC_SUCCESS: /* complete success */ break; default: fprintf(stderr, "processFilelist() returned unexpected rc = %d\n", rc); rc = RC_FAIL; break; } if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of doFSFileDeletions() ----------------*/ /* * NAME: getFilelistDeletions() * * FUNCTION: * Given two shadow files, each one containing the list of files * and directories of a filesystem at one point, determine the * list of files in the first shadow file which are not in the * second shadow file and put that list into a deletions file. * Return the full path name of the created file. * * PARAMETERS: * oldShadowFile: (IN) full path name of first shadow file * newShadowFile: (IN) full path name of second shadow file * fileDeletions: (OUT) flag indicating whether there were any * deleted files; a true value indicates * there were * deletionsFilelist: (OUT) pointer to name of file containing the list * of files which are in oldShadowFile but * are not in newShadowFile * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: */ int getFilelistDeletions(const char *oldShadowFile, const char *newShadowFile, Boolean *fileDeletions, char *deletionsFilelist) { Int32 fp_del, fp_old_shadow, fp_new_shadow; Int32 j, offset; Int32 rc1, rc = RC_SUCCESS; transactionsListRecord transactionRecord; backup_shadow_record_t oldShadowRecord; backup_shadow_record_t newShadowRecord; struct stat statBuf; Int32 bytesRead, bytesWritten; Int64 numberOfOldShadowRecords, numberOfNewShadowRecords; Int64 recordInOldShadow, recordInNewShadow; offset_t nextReadOldShadowOffset, actualOldShadowOffset; offset_t nextReadNewShadowOffset, actualNewShadowOffset; size_t shadowRecordSize = sizeof(backup_shadow_record_t); size_t transactionRecordSize = sizeof(transactionsListRecord); char* eq_p; Boolean shadowDeletions = false; Boolean searchNeeded = false; Boolean readNewRecord = true; char* fn = "getFilelistDeletions"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Stat the old backup shadow file. */ if (stat(oldShadowFile, &statBuf) == -1) { fprintf(stderr, "Could not stat file %s, errno=%d\n", oldShadowFile, errno); return(RC_FAIL); } /* Determine the number of records in the old shadow file. */ numberOfOldShadowRecords = statBuf.st_size / shadowRecordSize; /* Open the old backup shadow file. */ fp_old_shadow = open(oldShadowFile, O_RDONLY, 0644); if (fp_old_shadow == -1) { fprintf(stderr, "Cannot open file %s\n", oldShadowFile); return(RC_FAIL); } /* Stat the new backup shadow file. */ if (stat(newShadowFile, &statBuf) == -1) { fprintf(stderr, "Could not stat file '%s', errno=%d\n", newShadowFile, errno); return(RC_FAIL); } /* Determine the number of records in the new shadow file. */ numberOfNewShadowRecords = statBuf.st_size / shadowRecordSize; /* Open the new backup shadow file. */ fp_new_shadow = open(newShadowFile, O_RDONLY, 0644); if (fp_new_shadow == -1) { fprintf(stderr, "Cannot open file %s\n", newShadowFile); return(RC_FAIL); } /* Create the full pathname for the deletions file for the filesystem. */ snprintf(deletionsFile, MAX_FILE_NAME, "%s/%s", fsName, deletionsName); /* Create and open the deletions file in append mode. */ fp_del = open(deletionsFile, O_CREAT | O_APPEND | O_WRONLY | O_TRUNC, 0600); if (fp_del == -1) { fprintf(stderr, "Cannot open file %s\n", deletionsFile); return(RC_FAIL); } recordInOldShadow = 0; recordInNewShadow = 0; nextReadOldShadowOffset = recordInOldShadow*shadowRecordSize; actualOldShadowOffset = lseek(fp_old_shadow, recordInOldShadow*shadowRecordSize, SEEK_SET); if (actualOldShadowOffset != nextReadOldShadowOffset) { fprintf(stderr, "Cannot seek to %lld in %s\n", recordInOldShadow*shadowRecordSize, oldShadowFile); return(RC_FAIL); } nextReadNewShadowOffset = recordInNewShadow*shadowRecordSize; actualNewShadowOffset = lseek(fp_new_shadow, recordInNewShadow*shadowRecordSize, SEEK_SET); if (actualNewShadowOffset != nextReadNewShadowOffset) { fprintf(stderr, "Cannot seek to %lld in %s\n", recordInNewShadow*shadowRecordSize, newShadowFile); return(RC_FAIL); } /* Loop through the old shadow file. */ while (recordInOldShadow < numberOfOldShadowRecords) { /* Read a record from the old shadow file. */ bytesRead = read(fp_old_shadow, &oldShadowRecord, shadowRecordSize); if (bytesRead != shadowRecordSize) { fprintf(stderr, "Error reading from file descriptor %d\n", fp_old_shadow); return(RC_FAIL); } /* Indicate a search for a matching record in the new shadow file should be done. */ searchNeeded = true; /* Put a null character at the end of the file name to make it into a string so we can use strcmp(). */ offset = atoi((const char *) oldShadowRecord.tr.fnLength); eq_p = oldShadowRecord.tr.filename + offset; memset(eq_p, '\0', 1); /* Loop through the new shadow file. */ while (recordInNewShadow < numberOfNewShadowRecords) { if (readNewRecord) { /* Read a record from the new shadow file. */ bytesRead = read(fp_new_shadow, &newShadowRecord, shadowRecordSize); if (bytesRead != shadowRecordSize) { fprintf(stderr, "Error reading from file descriptor %d\n", fp_new_shadow); return(RC_FAIL); } /* Put a null character at the end of the file name to make it into a string so we can use strcmp(). */ offset = atoi((const char *) newShadowRecord.tr.fnLength); eq_p = newShadowRecord.tr.filename + offset; memset(eq_p, '\0', 1); } /* end of if (readNewRecord) */ /* Compare the records from the old and new shadow files. */ rc1 = strcmp((const char *) &oldShadowRecord.tr.filename, (const char *) &newShadowRecord.tr.filename); if (rc1 > 0) { /* We have not yet encountered a file in the new shadow file that comes lexicographically after the file from the old shadow file, so read the next record from the new shadow file. */ recordInNewShadow++; readNewRecord = true; continue; } if (rc1 == 0) { /* The names match. */ recordInOldShadow++; recordInNewShadow++; readNewRecord = true; searchNeeded = false; break; } if (rc1 < 0) { /* We just encountered a file in the new shadow file that comes lexicographically after the file from the old shadow file, so the file name does not exist in the newShadowFile (i.e., it must have been deleted). Construct a transaction record for the record from the old shadow file. */ memset((void *) &transactionRecord, ' ', transactionRecordSize); memcpy((void *) &transactionRecord.filename, (void *) &oldShadowRecord.tr.filename, strlen((const char *) &oldShadowRecord.tr.filename)); transactionRecord.delimeter2 = '\n'; /* Write the created transactions record to the deletions file. */ bytesWritten = write(fp_del, &transactionRecord, transactionRecordSize); if (bytesWritten != transactionRecordSize) { fprintf(stderr, "Error writing to %d; wrote %d bytes, not %d\n", fp_del, bytesWritten, transactionRecordSize); return(RC_FAIL); } shadowDeletions = true; recordInOldShadow++; readNewRecord = false; searchNeeded = false; break; } /* end of if (rc1 < 0) */ } /* end of while (recordInNewShadow < numberOfNewShadowRecords) */ /* Did we exhaust all of the records in the new shadow file in the course of a search? */ if (searchNeeded && recordInNewShadow == numberOfNewShadowRecords) { /* If we get here, it indicates that the old record was lexicographically greater than all of the records in the new shadow file. We will therefore copy the old record and all of the remaining old records to the deletions file. */ /* First, construct the transaction record for the current old shadow record. */ memset((void *) &transactionRecord, ' ', transactionRecordSize); memcpy((void *) &transactionRecord.filename, (void *) &oldShadowRecord.tr.filename, strlen((const char *) &oldShadowRecord.tr.filename)); transactionRecord.delimeter2 = '\n'; /* Write the created transaction record to the deletions file. */ bytesWritten = write(fp_del, &transactionRecord, transactionRecordSize); if (bytesWritten != transactionRecordSize) { fprintf(stderr, "Error writing to %d; wrote %d bytes, not %d\n", fp_del, bytesWritten, transactionRecordSize); return(RC_FAIL); } /* Set a flag to indicate that deletions were found. */ shadowDeletions = true; /* Copy all of the remaining old shadow file records to the deletions file and exit the loop. */ for (j = recordInOldShadow+1; j < numberOfOldShadowRecords; j++) { /* Read a record from the old shadow file. */ bytesRead = read(fp_old_shadow, &oldShadowRecord, shadowRecordSize); if (bytesRead != shadowRecordSize) { fprintf(stderr, "Error reading from file descriptor %d\n", fp_old_shadow); return(RC_FAIL); } /* Put a null character at the end of the file name to make it into a string so we can use strcmp(). */ offset = atoi((const char *) oldShadowRecord.tr.fnLength); eq_p = oldShadowRecord.tr.filename + offset; memset(eq_p, '\0', 1); /* Construct the transaction record for the shadow record. */ memset((void *) &transactionRecord, ' ', transactionRecordSize); memcpy((void *) &transactionRecord.filename, (void *) &oldShadowRecord.tr.filename, strlen((const char *) &oldShadowRecord.tr.filename)); transactionRecord.delimeter2 = '\n'; /* Write the created transactions record to the deletions file. */ bytesWritten = write(fp_del, &transactionRecord, transactionRecordSize); if (bytesWritten != transactionRecordSize) { fprintf(stderr, "Error writing to %d; wrote %d bytes, not %d\n", fp_del, bytesWritten, transactionRecordSize); return(RC_FAIL); } } /* end of for loop */ break; // exit the loop through the old shadow file } /* if (recordInNewShadow == numberOfNewShadowRecords) */ } /* end of while (recordInOldShadow < numberOfOldShadowRecords) */ /* Close the deletions file. */ rc = close(fp_del); if (rc == -1) { fprintf(stderr, "Cannot close file descriptor %d for file %s\n", fp_del, deletionsFile); return(RC_FAIL); } /* Close the old and new shadow files. */ rc = close(fp_old_shadow); if (rc == -1) { fprintf(stderr, "Cannot close file descriptor %d for file %s\n", fp_old_shadow, oldShadowFile); return(RC_FAIL); } rc = close(fp_new_shadow); if (rc == -1) { fprintf(stderr, "Cannot close file descriptor %d for file %s\n", fp_new_shadow, newShadowFile); return(RC_FAIL); } /* If there were deletions, set the deletions flag to true and pass it and the name of the deletions file back to the caller. */ if (shadowDeletions) { *fileDeletions = true; strncpy(deletionsFilelist, deletionsFile, MAX_FILE_NAME); } else { /* Indicate no deletions and delete the deletions file. */ *fileDeletions = false; unlink(deletionsFile); } if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of getFilelistDeletions() ----------------*/ /* * NAME: doFSFileChanges() * * FUNCTION: * Determine the files which have changed or which have been added * to a filesystem and back them up. * * PARAMETERS: * fsName: (IN) The full path name of the mounted filesystem. * fsSnapshotPathname: (IN) The full path name of a directory name * indicating where a snapshot of the filesystem * is to be stored. * * RETURNS: * RC_SUCCESS on success, RC_PSUCCESS on partial success, RC_FAIL on error. * * NOTES: */ int doFSFileChanges(char *fsName, char *fsSnapshotPathname) { Int32 tmp, rc = RC_SUCCESS; Int32 numShadows; char filenameTemp[MAX_FILE_NAME]; inodeBitsArray *inodeBitsArray2P; char* fn = "doFSFileChanges"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Do an incremental inode scan. Only the bits for inodes corresponding to files that have changed since the previous backup will be set. */ inodeBitsArray2P = &inodeBits2P; rc = doIncrInodeScan(fsSnapshotPathname, &prevSnapId, inodeBitsArray2P, backupControlP); if (rc != 0) { fprintf(stderr, "doFSFileChanges(): doIncrInodeScan() returned rc = %d\n", rc); return(RC_FAIL); } /* Create the full pathname for the changes file for the filesystem. */ snprintf(changesFile, MAX_FILE_NAME, "%s/%s", fsName, changesName); /* Using the bitmap to identify the changed files, extract the names of the changed files from the backup shadow file to create the changes file. */ rc = extractChangedFiles(backupShadowFile, *inodeBitsArray2P, &fileChanges, changesFile); if (rc != 0) { fprintf(stderr, "getFSFileChanges(): extractChangedFiles() returned rc = %d\n", rc); unlink(changesFile); return(RC_FAIL); } /* Process any changes that were found. */ if (fileChanges == true) { /* Rename the changes file to be the transactions file. */ tmp = rename(changesFile, transactionsList); if (tmp != 0) { fprintf(stderr, "rename(%s, %s) returned %d\n", changesFile, transactionsList, rc); return(RC_FAIL); } /* Invoke routine to process the transactions file. The false flag that is passed tells the routine to divy the transactions file among the client processes by number of files, not by aggregate file size, which is what makes sense when doing deletions. */ rc = processFilelist(fsName, transactionCmdSelective, transactionCmdOption, backupControlP, false); } else // There were no changes to process, so indicate success. { rc = RC_SUCCESS; } /* Process the return code. */ switch (rc) { case RC_FAIL: /* failure */ fprintf(stderr, "processFilelist() returned rc = %d\n", rc); break; case RC_PSUCCESS: /* partial success */ case RC_SUCCESS: /* complete success */ /* Rename the backup shadow file to be the old backup shadow file. */ tmp = rename(backupShadowFile, backupShadowCheck); if (tmp != 0) { fprintf(stderr, "rename(%s, %s) returned %d\n", backupShadowFile, backupShadowCheck, rc); return(RC_FAIL); } break; default: fprintf(stderr, "processFilelist() returned unexpected rc = %d\n", rc); rc = RC_FAIL; break; } if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of doFSFileChanges() ----------------*/ /* * NAME: extractChangedFiles() * * FUNCTION: * Extract the transaction records for the changed files from the * input file, returning them in the output file. * * PARAMETERS: * filein - (IN) the name of the file containing the pertinent * backup shadow information * bitMap - (IN) pointer to the bit map array that indicates * whether a given inode is for a changed file * fileChanges - (OUT) flag indicating whether there were any * deleted files; a true value indicates * there were * fileout - (IN) the name of the file where the extracted * transaction records should be written * * RETURNS: * RC_SUCCESS on success, RC_FAIL on error. * * NOTES: */ int extractChangedFiles(char *filein, inodeBitsArray bitMap, Boolean *fileChanges, char *fileout) { Int32 rc = RC_SUCCESS; Int32 bytesRead, bytesWritten; backup_shadow_record_t shadowRecord; transactionsListRecord transactionRecord; Int32 fp_in, fp_out; Int64 recordInFile; Int64 numberOfFiles; Int32 transactionRecordSize, shadowRecordSize; offset_t nextReadOffset, actualOffset; struct stat statBuf; inodeBitsArray myBitMap; char* fn = "extractChangedFiles"; if (Tracing) traceEntry("tsbackup.C", fn, __LINE__); /* Initializations */ myBitMap = bitMap; transactionRecordSize = sizeof(transactionsListRecord); shadowRecordSize = sizeof(backup_shadow_record_t); /* Stat and open the filein file. */ if (stat(filein, &statBuf) == -1) { fprintf(stderr, "Could not stat file '%s', errno=%d\n", filein, errno); return(RC_FAIL); } numberOfFiles = statBuf.st_size / shadowRecordSize; fp_in = open(filein, O_RDONLY, 0644); if (fp_in == -1) { fprintf(stderr, "extractChangedFiles(%s, %s): open(%s) failure\n", filein, fileout, filein); return(RC_FAIL); } /* Locate the first record in the filein file. */ recordInFile = 0; nextReadOffset = recordInFile*shadowRecordSize; actualOffset = lseek(fp_in, recordInFile*shadowRecordSize, SEEK_SET); if (actualOffset != nextReadOffset) { fprintf(stderr, "Cannot seek to %lld in %s\n", recordInFile*shadowRecordSize, filein); return(RC_FAIL); } /* Create and open the fileout file in append mode. */ fp_out = open(fileout, O_CREAT | O_APPEND | O_RDWR | O_TRUNC, 0600); if (fp_out == -1) { fprintf(stderr, "Cannot open file %s\n", fileout); return(RC_FAIL); } /* Read each record from the input file and get the specified file name. */ while (numberOfFiles > 0) { bytesRead = read(fp_in, &shadowRecord, shadowRecordSize); if (bytesRead != shadowRecordSize) { fprintf(stderr, "Error reading file %s\n", filein); return(RC_FAIL); } /* Check the bit map array entry to determine whether the inode must be backed up. */ if (testBit(myBitMap, (UInt32) atoi((const char *) shadowRecord.inodenum)) != 0) { /* Build the record to be written to the file. */ memset((void *) &transactionRecord, ' ', transactionRecordSize); memcpy((void *) &transactionRecord.filename, (void *) &shadowRecord.tr.filename, MAX_FILE_NAME); transactionRecord.delimeter2 = '\n'; /* Write the record to the file. */ bytesWritten = write(fp_out, &transactionRecord, transactionRecordSize); if (bytesWritten != transactionRecordSize) { fprintf(stderr, "Error writing to %d; wrote %d bytes, not %d\n", fp_out, bytesWritten, transactionRecordSize); return(RC_FAIL); } *fileChanges = true; } numberOfFiles--; } /* Close the input and output files. */ close(fp_in); close(fp_out); if (Tracing) traceExit("tsbackup.C", fn, __LINE__); return(rc); } /*------ end of extractChangedFiles ----------------*/