/* $Id: sbp.c,v 2.24 2012/09/28 14:30:08 ksb Exp $ * Actually do the sync operations. * We can use any method in the table, see aSMTable below */ #include #include #include #include #include #include #include #include #include #include #include "machine.h" #include "strdiff.h" #include "main.h" #include "read.h" #include "sbp.h" #include "dicer.h" #define WORK_TODO 0x001 /* still must process */ #define WORK_REPCHECK 0x010 /* check for duplicate block */ #define WORK_PASSCHECK 0x020 /* MetaCheck'd fsck pass */ /* return 1 if this exists and is a directory (ksb) */ int IsDir(pcNode) char *pcNode; { auto struct stat stNode; if (-1 == stat(pcNode, & stNode)) { if (ENOENT != errno) { fprintf(stderr, "%s: stat: %s: %s\n", progname, pcNode, strerror(errno)); iExitCode = EX_OSERR; } return 0; } if (S_IFDIR == (stNode.st_mode & S_IFMT)) { return 1; } return 0; } /* Embedded newfs options on the end of the line have (ksb) * to be separated from the line by a octothorpe and * start with a "-[A-Za-z]" after some white space. * /dev/device /home ufs 0 2 # -i 16384 -c 24 */ static char * NewfsOpts(pcRest) char *pcRest; { register char *pcHash; pcHash = strchr(pcRest, '#'); if ((char *)0 == pcHash) { return (char *)0; } do { ++pcHash; } while (isspace(*pcHash)); if ('-' != pcHash[0] || !isalpha(pcHash[1])) { return (char *)0; } return pcHash; } /* At 90% full of data we'd like the filesystem to have about 60% of (ksb) * the inodes allocated. These are tuneable parameters as well (-D/-I). * * The closer to 90% (or above) the fs is now the tighter we can * make the estimate of bytes/inode to get there. For lower targets * we hedge to the "more inodes" side (because running out of inodes * with space left is silly). -- ksb * * On the other side allocating more than 1 inode per frag is useless. * So we need to be sure that bytes-per-inode is >= frag-size. */ static int MakeOpts(pcWhere, pcOpts) char *pcWhere, *pcOpts; { auto STATFS_BUF SFOn; register long lFused, lBused, lBlocks; register long lMinFree, lBytePerBlock; register double dActComp, dFiles; register double dBytePerFile, dBytePerInode, dFudge; register int iMinFree; if (-1 == STATFS_CALL(pcWhere, & SFOn)) { return 0; } /* reproduce the df parameters I think in... */ lBused = SFOn.f_blocks - SFOn.f_bfree; lFused = SFOn.f_files - SFOn.f_ffree; dActComp = (100.0*(double)(lBused + SFOn.f_bavail)/(double)SFOn.f_blocks); lMinFree = (long) (100.50 - dActComp); #if HAVE_STATVFS lBytePerBlock = SFOn.f_frsize; #else lBytePerBlock = SFOn.f_bsize; #endif if (fInsane) { printf("For %s:\n", pcWhere); printf(" %ld + %ld = %ld (used + free = blocks)\n", (long)lBused, (long)SFOn.f_bfree, (long)SFOn.f_blocks); printf(" %ld (avail blocks of %ld bytes)\n", (long)SFOn.f_bavail, (long)lBytePerBlock); printf(" %ld + %ld = %ld (used + free = files)\n", (long)lFused, (long)SFOn.f_ffree, (long)SFOn.f_files); printf(" %2.4f active, with minfree %ld\n", dActComp, (long)lMinFree); } /* OK, we know everything, now compute the present bytes/inode */ dBytePerFile = (double)(lBused * lBytePerBlock) / (double)lFused; dFiles = ((double)SFOn.f_blocks * dBlockFull/100.0 * dActComp/100.0 * (double)lFused)/ ((double)lBused * dFileFull/100.0); dBytePerInode = (double)SFOn.f_blocks * (double)lBytePerBlock * dActComp/100.0 / dFiles; /* The closer to the target % we are now the better the * guess. If we are way low round the bytes/inode down * a "little". -- ksb */ dFudge = (double)SFOn.f_bavail/(double)SFOn.f_blocks; if (dFudge < 0.015021964) { dFudge = 0.0; } else if (dFudge > 0.18) { dFudge -= 0.06221965; } /* for sbp we know fudge can be a lot less */ dFudge /= 20.0; /* The lower bound on bytes/inode is hard to figure * and we are sometimes wrong here. ZZZ --ksb */ lBlocks = (dBytePerInode+0.99990)/512.0 /* - dFudge */; if (dBytePerInode > MAX_BPI) { sprintf(pcOpts, "-i %ld", (long)MAX_BPI); } else if (dBytePerInode < lBytePerBlock) { sprintf(pcOpts, "-i %ld", (long)lBytePerBlock); } else { sprintf(pcOpts, "-i %ld", 512L * (long)lBlocks); } /* Try to keep -b and -f options if we can, should be an option * to us to guess as new ones, or keep these. * we keep -m as well. All this has "poor control". */ #if HAVE_STATVFS if (0 != SFOn.f_bsize && 8192 != SFOn.f_bsize) { sprintf(pcOpts+strlen(pcOpts), " -b %ld", (long)SFOn.f_bsize); } if (0 != SFOn.f_frsize) { sprintf(pcOpts+strlen(pcOpts), " -f %ld", (long)SFOn.f_frsize); } #else if (0 != SFOn.f_bsize) { sprintf(pcOpts+strlen(pcOpts), " -b %ld", (long)SFOn.f_bsize); } #endif /* We should keep softupdates as well sblock.fs_flags |= FS_DOSOFTDEP; * To do that we must read the superblock -- I don't have a clean * way to do that (today, yet). * We also can't pass -nenable to newfs, we must call tunefs * How? Let the Newfs mk rule do it, because we can't. -- ksb */ /* If the partition is mostly full we might reduce minfree here? * When the source is >98.5% full and minfree >1% we should reduce * minfree by 1%. Can the default minfree change from "10"? LLL */ iMinFree = (int)(lMinFree + 0.10); if (((double)SFOn.f_bavail)/SFOn.f_blocks < 1.5 && iMinFree > 1) { --iMinFree; } if (10 != iMinFree) { sprintf(pcOpts+strlen(pcOpts), " -m %d", iMinFree); } if (fInsane) { printf("Computed:\n"); printf(" %f bytes/file presently\n", dBytePerFile); printf(" We'd make that %6.0f files at %7.0f bytes/inode\n", dFiles, dBytePerInode); printf(" ...and that gets a fudge factor of %f\n", dFudge); printf(" ...with %ld bytes/frag\n", lBytePerBlock); printf("Suggested newfs option \"%s\"\n", pcOpts); } return 1; } /* map /dev/dsk/c_t_d_s_ -> /dev/rdsk (ksb) * also has to work for /dev/md/dsk/d72 and the like */ static void MapRaw(pcOut, pcBlock) char *pcOut, *pcBlock; { register char *pcTok; register int iLen; if ((char *)0 != (pcTok = strstr(pcBlock, "/dsk/"))) { iLen = pcTok - pcBlock; sprintf(pcOut, "%*.*s/rdsk/%s", iLen, iLen, pcBlock, pcTok+5); return; } if ((char *)0 == (pcTok = strrchr(pcBlock, '/'))) { sprintf(pcOut, "/dev/rdsk/%s", pcBlock); return; } sprintf(pcOut, "/dev/r%s", pcBlock+5); } /* remember the method params the Customer suggested for "-opts" (ksb) * rsync:--verbose -> "--verbose" in %P */ static char *pcMethParams = (char *)0; static void ParamDash(pcOpts, pSMUsing) char *pcOpts; SBP_METHOD *pSMUsing; { if ((char *)0 != pcOpts && '?' == pcOpts[0] && '\000' == pcOpts[1]) { printf("%s: usage -H %s:%s\n", progname, pSMUsing->pcmeth, pSMUsing->pcparam); exit(EX_OK); } pcMethParams = ((char *)0 != pcOpts && '\000' == pcOpts[0]) ? (char *)0 : pcOpts; } /* for "dump" we have to remember the keys, then the params (ksb) * "dump:ub 126" -> "ub" in %K, "126" in %P * we take a const char array for inits */ static char *pcMethKeys = ""; static void ParamDump(pcKey, pSMUsing) char *pcKey; SBP_METHOD *pSMUsing; { if ((char *)0 == pcKey) { return; } if ((char *)0 != pcKey && '?' == pcKey[0] && '\000' == pcKey[1]) { printf("%s: usage -H %s:%s\n", progname, pSMUsing->pcmeth, pSMUsing->pcparam); exit(EX_OK); } pcMethKeys = strdup(pcKey); pcKey = pcMethKeys; while ('\000' != *pcKey && !isspace(*pcKey)) { ++pcKey; } if (isspace(*pcKey)) { *pcKey = '\000'; ParamDash(pcKey+1); } } /* Given a slice like /dev/rdsk/c5t2d3s7 find the C slice (ksb) * which is always slice 2 on solaris style, or given a BSD style * /dev/rwd0a return /dev/rwd0c. */ static void FindCSlice(pcHold) char *pcHold; { register int iLen, i; if ((char *)0 == pcHold || 0 == (iLen = strlen(pcHold))) { return; } for (i = 0; i < iLen && isdigit(pcHold[iLen-1-i]); ++i) { /* might be ...s3s17 in the future */; } /* If there are not digits on the end then it must be a BSD * style -- maybe; vinum screws the whole thing of course. */ if (0 != i) { (void)strcpy(pcHold+iLen-i, "2"); } else if (isdigit(pcHold[iLen-2])) { pcHold[iLen-1] = 'c'; } else { fprintf(stderr, "%s: %s: can't find c slice (slice 2)\n", progname, pcHold); iExitCode = EX_IOERR; } } static SBP_ENTRY **ppBPOrder = /* list of filesystems we backup */ (SBP_ENTRY **)0; /* Find a filesystem by name, or device (ksb) * {fs-spec} or [fs-spec] or * we are called starting at the "f" with the length of the fs-spec */ static SBP_ENTRY * FsFind(pcName, iLen) char *pcName; unsigned iLen; { register SBP_ENTRY *pBPRet; register int i; for (i = 0; (SBP_ENTRY *)0 != (pBPRet = ppBPOrder[i]); ++i) { if (0 == strncmp(pBPRet->pcmount, pcName, iLen) && '\000' == pBPRet->pcmount[iLen]) { break; } if (0 == strncmp(pBPRet->pcdev, pcName, iLen) && '\000' == pBPRet->pcdev[iLen]) { break; } if ((SBP_ENTRY *)0 == (pBPRet = pBPRet->pBPswap)) { continue; } if (0 == strncmp(pBPRet->pcmount, pcName, iLen) && '\000' == pBPRet->pcmount[iLen]) { break; } if (0 == strncmp(pBPRet->pcdev, pcName, iLen) && '\000' == pBPRet->pcdev[iLen]) { break; } } return pBPRet; } /* See if an option string has the given attribute (ksb) * no$Attr -> 0, $Attr -> 1, not specifiy -> -1 * arrtibutes are separated by a comma (,), as "clean,blue,noplastic" */ static int HasOpt(char *pcOpts, char *pcAttr) { register char *pcLook; unsigned int iLen; iLen = strlen(pcAttr); for (/*param */; (char *)0 != pcOpts; pcOpts = pcLook) { if ((char *)0 == (pcLook = strstr(pcOpts, pcAttr))) { break; } if ('\000' != pcLook[iLen] && ',' != pcLook[iLen]) { ++pcLook; continue; } if (pcLook == pcOpts || ',' == pcLook[-1]) { return 1; } if (pcLook == pcOpts+1 || 'o' != pcLook[-1] || 'n' != pcLook[-2]) { /* below */ } else if (pcLook == pcOpts+2 || ',' == pcLook[-3]) { return 0; } ++pcLook; } return -1; } static char *pcMkDb = /* table of default newfs/mkfs rules */ (char *)0; /* expand the command from a few escapes we find handy (ksb) * %% a percent * %X... our mirror (so %Xd is our mirror's devname) * %d devname (%u c slice we are on) * %i newfs tune options (-i, -c, -a, ...) from line, or * under a command line a good guess * %m mount point * %r raw devname (%w c slice our raw is on) * * %a the string "-o async" or nothing (see -a) * %f the fstab file we read for this run ("./thing") * %F the fstab file we know the system uses ("/etc/vfstab") * %D acDumpPath[] = "/sbin/dump"; * %R acRestorePath[] = "/sbin/restore"; * %I acInstallPath[] = "/usr/local/bin/install"; * * %K the keys part of the method params, if it has one (viz. dump) * %P any method suggested parameters * %M the marker we ask mk to index for a newfs call * %O the options to fssnap (-o fssnap-options) * %Q the path to mk * %T the table of mk rules to build filesystems * %{mt/pt}... look up the mount point and expand WRT to that * %{/usr}Xd is backup usr's device name * %[...] dicer applied to expansion * %(...) mixer applied to expansion * TODO: * %B /usr/platform/`uname -i`/lib/fs/ufs/bootblk (or the like): * the hard part is when there are different ones by drive-type * we need to pick, based on the drive-type of "/backup". ZZZ */ static int Expand(pcOut, puSize, pcIn, pBPSource) char *pcOut, *pcIn; unsigned *puSize; SBP_ENTRY *pBPSource; { register SBP_ENTRY *pBP; register char *pcTemp, *pcDicer, *pcMixer; register int iKeep; auto char *pcEntry; auto unsigned uCall; auto struct stat stDev; pcEntry = pcOut; pcDicer = pcMixer = (char *)0; while ('\000' != *pcIn) { if ('%' != *pcIn) { *pcOut++ = *pcIn++; continue; } pBP = pBPSource; *pcOut = '\000'; /* in cases of empty escapes */ for (++pcIn;;) { switch (iKeep = *pcIn++) { static char acAsync[] = "async"; register char *pcWhite, *pcRaw, cKeep; case '(': if ((char *)0 != pcMixer) { fprintf(stderr, "%s: no nested mixer expressions allowed\n", progname); return 1; } pcMixer = pcOut; continue; case '[': if ((char *)0 != pcDicer) { fprintf(stderr, "%s: no nested dicer expressions allowed\n", progname); return 1; } pcDicer = pcOut; continue; case 'a': if (!fAsync && (char *)0 == strstr(pBP->pcrest, acAsync) && (char *)0 == strstr(pBP->pBPswap->pcrest, acAsync)) { break; } sprintf(pcOut, "-o %s", acAsync); break; case 'f': (void)strcpy(pcOut, pcFstab); break; case 'F': (void)strcpy(pcOut, acFstabPath); break; case 'D': (void)strcpy(pcOut, acDumpPath); break; case 'Q': (void)strcpy(pcOut, acMkPath); break; case 'R': (void)strcpy(pcOut, acRestorePath); break; case 'I': (void)strcpy(pcOut, acInstallPath); break; case 'X': pBP = pBP->pBPswap; continue; case 'd': case 'u': if (6 == iTableType) { (void)strcpy(pcOut, pBP->pcdev); } else { for (pcWhite = pBP->pcdev; '\000' != *pcWhite && !isspace(*pcWhite); ++pcWhite) { /* nada */ } cKeep = *pcWhite; *pcWhite = '\000'; (void)strcpy(pcOut, pBP->pcdev); *pcWhite = cKeep; } if ('u' == iKeep) { FindCSlice(pcOut); } break; case 't': /* fs type from pcrest */ for (pcWhite = pBP->pcrest; isspace(*pcWhite); ++pcWhite) { ; } for (/* */; '\000' != *pcWhite && !isspace(*pcWhite); ++pcWhite) { *pcOut++ = *pcWhite; } *pcOut = '\000'; break; case 'i': /* find newfs option, on primary ot alternate * (they should be the same) */ pcTemp = NewfsOpts(pBP->pcrest); if ((char *)0 == pcTemp) { pcTemp = NewfsOpts(pBP->pBPswap->pcrest); } if ((char *)0 == pcTemp && fJudge) { if (MakeOpts(pBP->pBPswap->pcmount, pcOut)) { break; } } if ((char *)0 == pcTemp) { break; } (void)strcpy(pcOut, pcTemp); break; case 'm': (void)strcpy(pcOut, pBP->pcmount); break; case 'r': case 'w': if (6 == iTableType) { if (0 == strncmp(pBP->pcdev, "/dev/", 5)) { sprintf(pcOut, "/dev/r%s", pBP->pcdev+5); } else { sprintf(pcOut, "%s", pBP->pcdev); } if (-1 == stat(pcOut, & stDev)) { sprintf(pcOut, "%s", pBP->pcdev); } } else { /* type 7 */ for (pcWhite = pBP->pcdev; '\000' != *pcWhite && !isspace(*pcWhite); ++pcWhite) { /* nada */ } pcRaw = pcWhite; do { ++pcRaw; } while ('\000' != *pcRaw && isspace(*pcRaw)); cKeep = *pcWhite; *pcWhite = '\000'; if (0 == strcmp("-", pcRaw)) { MapRaw(pcOut, pBP->pcdev); } else { sprintf(pcOut, "%s", pcRaw); } if (-1 == stat(pcOut, & stDev)) { sprintf(pcOut, "%s", pBP->pcdev); } *pcWhite = cKeep; } if ('w' == iKeep) { FindCSlice(pcOut); } break; case 'K': if ((char *)0 == pcMethKeys) { break; } (void)strcpy(pcOut, pcMethKeys); break; case 'P': if ((char *)0 == pcMethParams) { break; } (void)strcpy(pcOut, pcMethParams); break; case 'M': /* -M mk marker for newfs/mkfs */ if ((char *)0 == pcMkNewfs || '\000' == *pcMkNewfs) { (void)strcpy(pcOut, "Newfs"); break; } (void)strcpy(pcOut, pcMkNewfs); break; case 'O': if ((char *)0 == pcFsSnapOpts) { break; } (void)strcpy(pcOut, pcFsSnapOpts); break; case 'T': if ((char *)0 != pcUserDb && '\000' != *pcUserDb) { (void)strcpy(pcOut, pcUserDb); break; } if ((char *)0 == pcMkDb || '\000' == *pcMkDb) { fprintf(stderr, "%s: %s: no mk table built\n", progname, pBP->pcmount); exit(EX_PROTOCOL); } (void)strcpy(pcOut, pcMkDb); break; case '{': /*} find fs by name or device */ for (pcTemp = pcIn; /*{*/'}' != *pcIn; ++pcIn) { if ('\000' == *pcIn) { /*{*/ fprintf(stderr, "%s: missing \"}\" in filesystem spec\n", progname); exit(EX_DATAERR); } } if ((SBP_ENTRY *)0 == (pBP = FsFind(pcTemp, pcIn-pcTemp))) { fprintf(stderr, "%s: %.*s: not in filesystems to backup\n", progname, pcIn-pcTemp, pcTemp); exit(EX_DATAERR); } continue; default: fprintf(stderr, "%s: '%c': unknown expansion in command\n", progname, pcIn[-1]); /* fall though */ case '%': *pcOut++ = pcIn[-1]; *pcOut = '\000'; break; } break; } pcOut += strlen(pcOut); if ((char *)0 != pcDicer) { *pcOut = '\000'; pcOut = pcDicer; pcDicer = strdup(pcOut); *pcOut = '\000'; pcTemp = pcIn; uCall = *puSize - (pcOut - pcEntry); if ((char *)0 == (pcIn = Dicer(pcOut, &uCall, pcIn, pcDicer))) { fprintf(stderr, "%s: dicer failed \"%s\"\n", progname, pcTemp); return 1; } free((void *)pcDicer); pcOut += strlen(pcOut); pcDicer = (char *)0; } if ((char *)0 != pcMixer) { uCall = *puSize - (pcOut - pcEntry); pcTemp = pcIn; if ((char *)0 == (pcIn = Mixer(pcMixer, &uCall, pcIn, ')'))) { fprintf(stderr, "%s: mixer failed: %s\n", progname, pcTemp); return 2; } pcOut += strlen(pcOut); pcMixer = (char *)0; } } *pcOut = '\000'; return 0; } /* Run the given command (ksb) * first expand it to fill in the params, then print it to a shell */ static int Run(pcFmt, pBP) char *pcFmt; SBP_ENTRY *pBP; { auto char acCmd[MAXPATHLEN*4+10240]; auto unsigned uSize; uSize = sizeof(acCmd); if (0 != Expand(acCmd, &uSize, pcFmt, pBP)) { return 1; } if (fVerbose) { printf("%s\n", acCmd); } if (fExec) { return system(acCmd); } return 0; } /* make a mount-point, mode 555 root.0 or parent dir group (ksb) */ static int MkMtPoint(pBP) SBP_ENTRY *pBP; { if (fExec && IsDir(pBP->pcmount)) return 0; return Run("[ -d \"%m\" ] || mkdir -p \"%m\"", pBP); } /* umount a partition and sync the filesystems (ksb) */ static int UmountPartition(pBP) SBP_ENTRY *pBP; { auto struct stat stTarget, stAbove; register char *pcSlash; register int i; if (0 == HasOpt(pcMethParams, "mount")) return 0; if (!fExec || (char *)0 == (pcSlash = strrchr(pBP->pcmount, '/'))) { /* try it */ } else { *pcSlash = '\000'; i = stat(pBP->pcmount == pcSlash ? "/" : pBP->pcmount, & stAbove); *pcSlash = '/'; if (-1 == i) { if (ENOENT == errno) { return /* not mounted - no parent */0; } } else if (-1 == stat(pBP->pcmount, & stTarget)) { if (ENOENT == errno) { return /* not mounted -- not existant */0; } } else if (stTarget.st_dev == stAbove.st_dev) { return /* not mounted -- same device */0; } } if (0 == HasOpt(pcMethParams, "force")) { return Run("umount -f %m", pBP); } return Run("umount %m", pBP); } static char *pcNewfsRule = /* if we can't us mk we try this direct */ (char *)0; /* unlink the mk ruleset if we built it (ksb) */ static void DestroyMkTable() { if ((char *)0 == pcMkDb) { return; } if (fVerbose) { printf("%s: rm %s\n", progname, pcMkDb); } if (fExec && 0 != unlink(pcMkDb)) { fprintf(stderr, "%s: unlink: %s: %s\n", progname, pcMkDb, strerror(errno)); /* don't fail on this, we can leave some trash in /tmp */ } free((void *)pcMkDb); pcMkDb = (char *)0; } /* Create the list of default options for newfs/mkfs'ing each slice. (ksb) * If the driver gave us one we don't build ours. */ static void CreateMkTable(ppBP, iCount) SBP_ENTRY **ppBP; int iCount; { register char *pcLine; register int iFd, iMaxLen; auto unsigned uSize; auto char acCmd[MAXPATHLEN*4+10240]; if ((char *)0 == pcNewfsRule) { #if USE_MKFS pcNewfsRule = "mkfs %i %r\\nif [ _ext3 = _%t ]; then tune2fs -j %d; fi"; #else #if NEED_NEWFS_YES pcNewfsRule = "echo \"yes\" |newfs %i %r"; #else pcNewfsRule = "newfs %i %r"; #endif #endif } if ((char *)0 != pcUserDb && '\000' != *pcUserDb) { return; } /* allocate the rule + about 10 chars for the "$%M(%r): \n" + \000 */ iMaxLen = (strlen(pcNewfsRule)|15)+17; pcLine = malloc(iMaxLen); #if HAVE_SNPRINTF snprintf(pcLine, iMaxLen, "$%%M(%%r): %s\n", pcNewfsRule); #else sprintf(pcLine, "$%%M(%%r): %s\n", pcNewfsRule); #endif pcMkDb = strdup("/tmp/smkdbXXXXXX"); iFd = mkstemp(pcMkDb); if (-1 == iFd) { fprintf(stderr, "%s: open: %s: %s\n", progname, pcMkDb, strerror(errno)); exit(EX_NOINPUT); } if (fVerbose) { printf("%s: cat <<\\! >%s\n", progname, pcMkDb); } for (/* nada */; iCount > 0; --iCount, ++ppBP) { uSize = sizeof(acCmd); if (0 != Expand(acCmd, &uSize, pcLine, ppBP[0])) { exit(EX_USAGE); } if (fVerbose) { printf("%s", acCmd); } iMaxLen = strlen(acCmd); if (iMaxLen != write(iFd, acCmd, iMaxLen)) { fprintf(stderr, "%s: write: %d: %s\n", progname, iFd, strerror(errno)); exit(EX_OSERR); } } if (fVerbose) { printf("!\n%s: trap 'rm %s' EXIT\n", progname, pcMkDb); } close(iFd); fflush(stdout); free((void *)pcLine); atexit(DestroyMkTable); } /* for backup methods w/o any prereq files (ksb) */ static void CreateNada(ppBP, iCount) SBP_ENTRY **ppBP; int iCount; { /* nothing */; } /* run the local mkfs interface (ksb) * We (in sbp 2.x) indirect through the mk(1L) program to find the * command to run. We build a mk template in /tmp that we include to * catch any fs not in the fstab file as a marked line. * Since we run mk with 4 parameters: * mk -DFS_CLUE='%i' -m%M -d'%r' -t/tmp/$our_tfile %f * In mk term we see (in the marked command): * %b mk (the name we called mk with) * $FS_CLUE options we might pass newfs (under sbp -j) * %m the value of -M, default "Newfs" (see Expand +160) * %s the name of the raw device * %T includes our template db, or -T's param * In the fstab (checklist, vfstab, disklist) file include a line like * (assume no -M option to sbp): * $Newfs: mkext6 -C 64 -t 97 %s * Which calls the local "mkext6" program with some options on the * raw partition sbp passed to mk (as -d$raw which becomes %s in mk). */ static int NewFs(pBP) SBP_ENTRY *pBP; { #if NEED_VFSTAB_NOCHECK /* Bypass Solaris 10 braindead mkfs checks --jad * This also lets us shoot at our foot (removes the mount checks). */ setenv("NOINUSE_CHECK", "1", 0); #endif if ((char *)0 != pcMkRule && '\000' != *pcMkRule) { return Run(pcMkRule, pBP); } return Run(pcNewfsRule, pBP); } /* run fsck on the raw partition (ksb) */ static int CheckFs(pBP) SBP_ENTRY *pBP; { return Run("fsck -y %d", pBP); } /* sync a partition with dump/restore * build newfs command -- we could guess at inode tune (?) * build mount command * build the dump | restore pipeline * remove the trash * unmount that Bad Boy. */ static int CopyDumpRestore(pBP) SBP_ENTRY *pBP; { register int iRet; if (0 != (iRet = NewFs(pBP))) return iRet; if (0 != (iRet = MkMtPoint(pBP))) return iRet; if (0 != (iRet = Run("mount %a %d %m", pBP))) return iRet; iRet = Run("%D 0" #if USE_AUTOSIZE "a" #endif "f%K - %P %Xd | ( cd %m && %R -rf -)", pBP); Run("rm -f %m/restoresymtable", pBP); return iRet; } #if HAVE_FSSNAP /* like dump/restore but put a snap device in the mix (ksb) * Works on anything after FreeBSD 5.1, I think. It is super * strange that the mksnap_ffs mounts a fake "plain file", and we * use "rm" to unmount it. I'm not sure I believe it, or like it. */ static int CopyFsSnap(pBP) SBP_ENTRY *pBP; { register int iRet; register char *pcFalse; if (0 != (iRet = NewFs(pBP))) return iRet; if (0 != (iRet = MkMtPoint(pBP))) return iRet; if (0 != (iRet = Run("mount %a %d %m", pBP))) return iRet; /* LLL check source fstype for a better error message -- ksb * ZZZ rm any existsing ".sbp" file? Can I tell is it a snap? * The only way to see it as a snap is to mount another and see * is the file in question shows up as zero length, AFAICT. */ if (0 != (iRet = Run("mksnap_ffs -o %O %Xm %Xm/.sbp", pBP))) { fprintf(stderr, "%s: mksnap_ffs: not supported here\n", progname); iExitCode = EX_SOFTWARE; return iRet; } iRet = Run("%D 0" #if USE_AUTOSIZE "a" #endif "f%K - %P %Xm/.sbp` |(cd %m && %R -rf -)", pBP); /* I really think unmount makes more sense than rm here --ksb */ Run("rm -f %Xm/.sbp", pBP); if (0 == iRet) { Run("rm -f %m/restoresymtable", pBP); } return iRet; } #endif /* copy with cpio, the Old Way (ksb) * newfs the partition * mount it * use cpio -p and find */ static int CopyCpio(pBP) SBP_ENTRY *pBP; { register int iRet; if (0 != (iRet = NewFs(pBP))) return iRet; if (0 != (iRet = MkMtPoint(pBP))) return iRet; if (0 != (iRet = Run("mount %a %d %m", pBP))) return iRet; return Run("cd %Xm && find . -xdev -depth -print |cpio -pdm%K %P %m", pBP); } /* copy the raw partition with dd (ksb) * this is _never_ recommended for filesystems */ static int CopyDD(pBP) SBP_ENTRY *pBP; { register int iRet; if ((char *)0 == pcMethParams) { pcMethParams = "bs=126b"; } if (0 != (iRet = Run("dd if=%Xr of=%d %P", pBP))) return iRet; if (0 != (iRet = CheckFs(pBP))) return iRet; return Run("mount %d %m", pBP); } /* copy with the rsync program, only cool if the filesystem was (ksb) * previously copied with something else to get it newfs'd */ static int CopyRsync(pBP) SBP_ENTRY *pBP; { register int iRet; if (0 != (iRet = MkMtPoint(pBP))) return iRet; if (0 != (iRet = Run("mount %a %d %m", pBP))) return iRet; return Run("rsync -a -xHS --delete --numeric-ids %P %Xm/ %m", pBP); } /* copy with GNU tar, old style tar won't honor device boundries (ksb) * or copy device nodes. */ static int CopyTar(pBP) SBP_ENTRY *pBP; { register int iRet; if (0 != (iRet = NewFs(pBP))) return iRet; if (0 != (iRet = MkMtPoint(pBP))) return iRet; if (0 != (iRet = Run("mount %a %d %m", pBP))) return iRet; return Run("tar --create --file - --one-file-system -C %Xm . | tar xpf - --sparse %P -C %m", pBP); } /* Copy the filesytem by running mk on the filesystem table to (ksb) * extract the correct command to backup the data. */ static int CopyMk(pBP) SBP_ENTRY *pBP; { register int iRet; auto char acFmt[MAXPATHLEN*9]; /* 0 . * -t -T's table name or the default newfs we built * -m source mount-point * -d destination mount-point * 1 "async" or the empty string (-a) * 2 the target device (clue or nfs mount) * 3 the target fs type * 4 the newfs options comment text from the line (# -...) * or the primary's similar comment * then method params can override these options * then search the filesystem table we used for markup * * If you put single quotes in any of these you can "confuse" this: */ return Run("%Q -l0 -0 1.4 -t '%T' -m'%Xm' -d'%m' -1 '%a' -2 '%d' -3 '%t' -4 '%Xi' %P %F", pBP); } /* Just mount the current partitions (ksb) * use "-Hmount:empty" to newfs them * use "-Hsane" to unmount * use "-Hsane:fsck" to check for valid filesystems * use "-Hsane:nomount" to check for fstab only (no root action) */ static int BlankFs(pBP) SBP_ENTRY *pBP; { register int iRet; if (0 == HasOpt(pcMethParams, "mount")) return 0; if (1 == HasOpt(pcMethParams, "empty") && 0 != (iRet = NewFs(pBP))) return iRet; if (1 == HasOpt(pcMethParams, "fsck") && 0 != (iRet = CheckFs(pBP))) return iRet; if (0 != (iRet = MkMtPoint(pBP))) return iRet; return Run("mount %a %d %m", pBP); } /* Report on the devices used by this pair (ksb) * We do not handle NFS backups very well (at all). * + mulitple use of a disk partition (as backup or primary) * + missing raw names for primary partitions on a Sun (not fsck'd) * + strange spelling differences in block-vs-raw names (if we have them) * + the bug where the raw device is given as "-" (no fsck) * + missing device nodes (raw and block) * + ignore nfs specials, I don't care if you mount it twice even */ static void MetaRep(FILE *fp, SBP_ENTRY *pBP) { register char *pcBlock, *pcDel, *pcEOS; register int cEnd, cLast; auto char acRaw[MAXPATHLEN+4]; auto struct stat stExist; static char **ppcCmp = (char **)0; /* Reset state only */ if ((SBP_ENTRY *)0 == pBP) { if ((char **)0 != ppcCmp) { free((void *)ppcCmp); } ppcCmp = (char **)0; return; } pBP->wflags |= WORK_REPCHECK; cLast = cEnd = '\000'; pcEOS = pcDel = (char *)0; pcBlock = pBP->pcdev; while (isspace(*pcBlock)) { ++pcBlock; } /* Just ignore nfs mounted partitions for this report */ if ((char *)0 != strchr(pcBlock, ':')) { return; } if (7 == iTableType) { register char *pcCharacter, **ppcDiff; for (pcDel = pcBlock; '\000' != (cEnd = *pcDel); ++pcDel) { if (isspace(cEnd)) break; } *pcDel = '\000'; for (pcCharacter = pcDel+1; isspace(*pcCharacter); ++pcCharacter) ; for (pcEOS = pcCharacter+1; !isspace(*pcEOS); ++pcEOS) if ('\000' == *pcEOS) break; cLast = *pcEOS; *pcEOS = '\000'; ppcDiff = StrDiff(pcCharacter, pcBlock); if ('-' == pcCharacter[0] && pcEOS == pcCharacter+1) { MapRaw(acRaw, pcBlock); fprintf(stderr, "%s: %s(%d): raw device name \"-\" turns off fsck, replace with \"%s\"\n", progname, pBP->pcmount, pBP->iline, acRaw); iExitCode = EX_PROTOCOL; } else { if (-1 == stat(pcCharacter, &stExist) && ENOENT == errno) { fprintf(stderr, "%s: %s(%d): %s: %s\n", progname, pBP->pcmount, pBP->iline, pcCharacter, strerror(errno)); iExitCode = EX_OSERR; } else { fprintf(fp, "%s %s(%d)\n", pcCharacter, pBP->pcmount, pBP->iline); } if ((char **)0 == ppcCmp) { ppcCmp = ppcDiff; } else if (0 == strcmp(ppcCmp[0], ppcDiff[0]) && 0 == strcmp(ppcCmp[0], ppcDiff[0])) { free((void *)ppcDiff); } else { fprintf(stderr, "%s: %s(%d): devices might be spelled wrong (\"%s\" vs \"%s\" and \"%s\" vs \"%s\")\n", progname, pBP->pcmount, pBP->iline, ppcCmp[0], ppcDiff[0], ppcCmp[1], ppcDiff[1]); iExitCode = EX_DATAERR; } } } if (-1 == stat(pcBlock, &stExist) && ENOENT == errno) { fprintf(stderr, "%s: %s(%d): %s: %s\n", progname, pBP->pcmount, pBP->iline, pcBlock, strerror(errno)); iExitCode = EX_OSERR; } else { fprintf(fp, "%s %s(%d)\n", pcBlock, pBP->pcmount, pBP->iline); } if ((char *)0 != pcDel) { *pcDel = cEnd; } if ((char *)0 != pcEOS) { *pcEOS = cLast; } } /* Report if a mount point above us is not shadow'd on the backup (ksb) * (don't report /, we'd do that for every partition, we do that one * specially in the caller). */ static void MetaMissing(char *pcPath) { register char *pcTail; register int iFound; register SBP_ENTRY *pBP; if ((char *)0 == pcPath || '\000' == pcPath[0] || (char *)0 == (pcTail = strrchr(pcPath, '/'))) { return; } while (pcTail > pcPath && '/' == pcTail[-1]) --pcTail; if (pcPath == pcTail) return; *pcTail = '\000'; iFound = IsMountPt(pcPath); /* Some slight chicanery here, we put the slash back so we can avoid * finding ourselves in the back partition table. */ *pcTail = '/'; pBP = FsFind(pcPath, pcTail-pcPath); switch (iFound) { case -1: fprintf(stderr, "%s: stat: %.*s: %s\n", progname, pcTail-pcPath, pcPath, strerror(errno)); iExitCode = EX_OSERR; break; case 0: /* Presently read.c prevents us from getting here -- ksb */ if ((SBP_ENTRY *)0 != pBP) { fprintf(stderr, "%s: %s: is a backup for a plain direcrory\n", progname, pBP->pcmount); iExitCode = EX_UNAVAILABLE; } break; case 1: /* Presently read.c makes it impossible to have just the * backup for a plain directory, I might fix that. You could * have a Very Large slice you want to backup on many smaller * slices. "/big" -> "/backup/big", "/backup/big/"{f,i,g,h,t} * current code wont do that, but maybe we should. * * We'd have to group the copies to mount them all before * we started the copy from the source, this would only * work for all but -Hdd. The reverse operation should * be possible. Food for thought. LLL */ if ((SBP_ENTRY *)0 == pBP) { fprintf(stderr, "%s: %.*s: is mounted, but lacks a backup in %s (suggested by %s)\n", progname, pcTail-pcPath, pcPath, pcFstab, pcPath); iExitCode = EX_UNAVAILABLE; break; } break; } *pcTail = '\000'; /* Carp about missing mount above this one even, unlikely as that is */ MetaMissing(pcPath); *pcTail = '/'; } #if !defined(SBP_MAX_REST) #define SBP_MAX_REST 5 #endif /* Try to parse the rest of the line's option (ksb) * fstype pass-number at-boot options # solaris * fstype options dump pass-number # bsd/Linux * comments with no space before the # are a bummer (ufs rw 0 4#ouch) */ static void SplitOpts(char **ppcCols, char *pcOrig) { register unsigned uCol; while (isspace(*pcOrig)) ++pcOrig; for (uCol = 0; '\000' != *pcOrig && uCol < SBP_MAX_REST; ) { ppcCols[uCol++] = pcOrig; while ('\000' != *pcOrig && !isspace(*pcOrig)) { if ('#' == *pcOrig) { *pcOrig = '\000'; break; } ++pcOrig; } if ('\000' == *pcOrig) { break; } do { *pcOrig++ = '\000'; } while (isspace(*pcOrig)); if ('#' == *pcOrig) { break; } } ppcCols[uCol] = pcOrig; } /* See if the Solaris "mount at boot" flag is set correctly (ksb) * + yes for /, /usr and /var is wronhg * + yes for /backups is wrong * + no for primary slices is wrong * + anything else is a spelling error */ static void MetaAtBoot(SBP_ENTRY *pBPPrim, char *pcPrim, char *pcAlt) { static char *apcForce[] = {"/", "/usr", "/var", (char *)0 }; register char **ppc; for (ppc = apcForce; (char *)0 != *ppc; ++ppc) { if (0 == strcmp(*ppc, pBPPrim->pcmount)) break; } if (0 == strcmp("no", pcPrim)) { if ((char *)0 == *ppc) { fprintf(stderr, "%s: %s should be mounted at boot\n", progname, pBPPrim->pcmount); iExitCode = EX_DATAERR; } } else if (0 == strcmp("yes", pcPrim)) { if ((char *)0 != *ppc) { fprintf(stderr, "%s: %s: redundantly marked \"yes\" for mount-at-boot\n", progname, pBPPrim->pcmount); iExitCode = EX_DATAERR; } } else { fprintf(stderr, "%s: %s: \"%s\" unknown mount-at-boot flag\n", progname, pBPPrim->pcmount, pcPrim); iExitCode = EX_DATAERR; } if (0 == strcmp("no", pcAlt)) { /* OK, I like that */ } else if (0 == strcmp("yes", pcAlt)) { fprintf(stderr, "%s: %s: should not be marked for mount-at-boot\n", progname, pBPPrim->pBPswap->pcmount); iExitCode = EX_DATAERR; } else { fprintf(stderr, "%s: %s: \"%s\" unknown mount-at-boot flag\n", progname, pBPPrim->pBPswap->pcmount, pcAlt); iExitCode = EX_DATAERR; } } /* Check the fsck pass numbers (ksb) * We don't do backups, see the end, we could do even better here. LLL */ static void MetaPass(SBP_ENTRY *pBPPrim, char *pcPrim, char *pcAlt, int iColumn) { register char *pcTail, *pcUp; register int iCheck; auto char *pcMem; auto char *apcAbove[SBP_MAX_REST]; static char acInit[4] = "/"; register SBP_ENTRY *pBPAbove; pBPAbove = (SBP_ENTRY *)0; pcUp = pcTail = acInit; while ((SBP_ENTRY *)0 == pBPAbove) { *pcTail = '\000'; if ((char *)0 == (pcUp = strrchr(pBPPrim->pcmount, '/'))) { if ('\000' != pBPPrim->pcmount[0]) { fprintf(stderr, "%s: %s: doesn't look like a valid mount point (no slashes)\n", progname, pBPPrim->pcmount); iExitCode = EX_DATAERR; } *pcTail = '/'; return; } *pcTail = '/'; while (pcUp > pBPPrim->pcmount && '/' == pcUp[-1]) --pcUp; if (pcUp == pBPPrim->pcmount) { pBPAbove = FsFind("/", 1); } else { pBPAbove = FsFind(pBPPrim->pcmount, pcUp - pBPPrim->pcmount); } pcTail = pcUp; } pcMem = strdup(pBPAbove->pcrest); SplitOpts(apcAbove, pcMem); iCheck = atoi(apcAbove[iColumn]); if ('/' == pBPPrim->pcmount[0] && '\000' == pBPPrim->pcmount[1]) { if (1 != atoi(pcPrim)) { fprintf(stderr, "%s: /: should be on fsck pass 1\n", progname); iExitCode = EX_DATAERR; } } else if (iCheck > atoi(pcPrim)) { fprintf(stderr, "%s: %s: fsck pass number %s less than parent partition (%s at %s)\n", progname, pBPPrim->pcmount, pcPrim, pBPAbove->pcmount, apcAbove[iColumn]); iExitCode = EX_DATAERR; } else if (1 == atoi(pcPrim) && (char *)0 == strchr(pBPPrim->pcmount+1, '/')) { /* We'll let top-level mounts happen on pass 1, unless you * ask for it. */ if (1 == HasOpt(pcMethParams, "strict")) { fprintf(stderr, "%s: %s: fsck pass 1 should be reserved for the root partition\n", progname, pBPPrim->pcmount); iExitCode = EX_TEMPFAIL; } } else if (iCheck == atoi(pcPrim)) { fprintf(stderr, "%s: %s: fsck pass number %s should be greater than parent partition (%s at %s)\n", progname, pBPPrim->pcmount, pcPrim, pBPAbove->pcmount, apcAbove[iColumn]); iExitCode = EX_DATAERR; } free((void *)pcMem); pcMem = strdup(pBPAbove->pBPswap->pcrest); SplitOpts(apcAbove, pcMem); iCheck = atoi(apcAbove[iColumn]); if ((char *)0 == pcAlt || '\000' == *pcAlt) { /* No alternate to compare against, some partitions are not * important enough to sbp, like /var/crash. --ksb */ } else if (iCheck > atoi(pcAlt)) { fprintf(stderr, "%s: %s: fsck pass number %s less than parent partition (%s at %s)\n", progname, pBPPrim->pBPswap->pcmount, pcAlt, pBPAbove->pBPswap->pcmount, apcAbove[iColumn]); iExitCode = EX_DATAERR; } else if (1 != HasOpt(pcMethParams, "strict")) { /* ignore strict checks below */; } else if (0 == strcmp(pcMtPoint, pBPAbove->pBPswap->pcmount)) { /* Since we don't mount it at boot any pass number is OK for * the backup root. If we are strict we'll force it to F(/)+1 * Really should be the parent +1, I think. We can't always * see that, the parent partition might not be backed up. (ksb) */ if (!(1 < atoi(pcAlt))) { fprintf(stderr, "%s: %s: should be on fsck pass number greather than 1 \n", progname, pcMtPoint); iExitCode = EX_TEMPFAIL; } } else if (iCheck == atoi(pcAlt)) { fprintf(stderr, "%s: %s: fsck pass number %s equal to parent partition (%s at %s)\n", progname, pBPPrim->pBPswap->pcmount, pcAlt, pBPAbove->pBPswap->pcmount, apcAbove[iColumn]); iExitCode = EX_TEMPFAIL; } /* Should alternate partitions (not mounted) be stacked like primary? * Yes, but that's not a lint error because they are always marked * `no auto' so the mount/pass numbers hardly matter. And they should * be > slash, if you're asking me. -- ksb */ free((void *)pcMem); } /* Look at "noauto" and the like (ksb) * + primary should not be mounted read-only (ro, read-only) * + backup should not be mounted read-only (ro, read-only) * + backup should be "noauto" or something like that * + readonly/ro is bad * + explicit largefiles must match * + nosetuid -> nosetuid on backup */ static void MetaOpts(SBP_ENTRY *pBPPrim, char *pcPrim, char *pcAlt) { register int iPrim, iAlt; static char acAuto[] = "auto"; static char acLargeFiles[] = "largefiles"; static char acSetuid[] = "setuid"; if (-1 == (iPrim = HasOpt(pcPrim, "readonly"))) { iPrim = HasOpt(pcPrim, "ro"); } if (-1 == (iAlt = HasOpt(pcAlt, "readonly"))) { iAlt = HasOpt(pcAlt, "ro"); } switch (iPrim) { case 1: fprintf(stderr, "%s: %s(%d): primary partition is marked read only, why back it up?\n", progname, pBPPrim->pcmount, pBPPrim->iline); iExitCode = EX_DATAERR; break; default: break; } switch (iAlt) { case 1: fprintf(stderr, "%s: %s(%d): backup partition is marked read only, hard to update the contents\n", progname, pBPPrim->pBPswap->pcmount, pBPPrim->pBPswap->iline); iExitCode = EX_DATAERR; break; default: break; } /* Check for largefiles, if primary has it set backup should */ iPrim = HasOpt(pcPrim, acLargeFiles); iAlt = HasOpt(pcAlt, acLargeFiles); if (iPrim == iAlt) { /* same is always, OK */ } else if (-1 != iPrim || -1 != iAlt) { fprintf(stderr, "%s: %s(%d): primary and alternate should both be marked \"%s%s\"\n", progname, pBPPrim->pBPswap->pcmount, pBPPrim->pBPswap->iline, (-1 == iPrim ? iAlt : iPrim) == 0 ? "no" : "", acLargeFiles); iExitCode = EX_DATAERR; } /* Check for nosetuid, if primary has it set backup should */ iPrim = HasOpt(pcPrim, acSetuid); iAlt = HasOpt(pcAlt, acSetuid); if (iPrim == iAlt) { /* same is always, OK */ } else if (0 == iPrim) { fprintf(stderr, "%s: %s(%d): primary partition is marked \"no%s\", but we are not\n", progname, pBPPrim->pBPswap->pcmount, pBPPrim->pBPswap->iline, acSetuid); iExitCode = EX_DATAERR; } /* Don't check "auto" on hosts with the mount-at-boot column */ if (7 == iTableType) { return; } switch (HasOpt(pcPrim, acAuto)) { case -1: case 1: break; default: fprintf(stderr, "%s: %s(%d): primary partition is marked \"no%s\", we need it mounted to sync it\n", progname, pBPPrim->pcmount, pBPPrim->iline, acAuto); iExitCode = EX_DATAERR; break; } switch (HasOpt(pcAlt, acAuto)) { case -1: fprintf(stderr, "%s: %s(%d): backup partition should be marked \"no%s\"\n", progname, pBPPrim->pBPswap->pcmount, pBPPrim->pBPswap->iline, acAuto); iExitCode = EX_DATAERR; break; case 1: fprintf(stderr, "%s: %s(%d): backup partition is marked \"%s\", a backup will leave it unmounted\n", progname, pBPPrim->pBPswap->pcmount, pBPPrim->pBPswap->iline, acAuto); iExitCode = EX_DATAERR; break; case 0: break; } } /* Backup should be same fs type as primary, or nfs I guess (ksb) * We'll allow sshfs and rsync as a virtural fs type too. */ static void MetaType(SBP_ENTRY *pBPPrim, char *pcPrim, char *pcAlt) { if (0 == strcmp(pcPrim, pcAlt)) return; if (0 == strcmp("nfs", pcAlt) || 0 == strcmp("rsync", pcAlt) || 0 == strcmp("sshfs", pcAlt)) return; fprintf(stderr, "%s: %s: primary and alternate have different filesystem types (%s vs %s)\n", progname, pBPPrim->pcmount, pcPrim, pcAlt); iExitCode = EX_USAGE; } /* Check the configuration for simple (and other) problems (ksb) * + missing root mount point for backups * + filesystems we need which are not mounted (presently) * + no backup for slash (/) * We should also check: * - radically different size partitions (primary and backup) * - no superblock on the backup device !? ZZZ (see softupdates above) */ static int MetaCheck(int iAlts, SBP_ENTRY **ppBPCheck) { register int iRet, iScan; register FILE *fp; register char *pcTail; register SBP_ENTRY *pBPPrim; auto int fSawSlash; auto struct stat stTop; auto char *apcPrim[SBP_MAX_REST], *apcAlt[SBP_MAX_REST]; if (-1 == stat(pcMtPoint, & stTop)) { fprintf(stderr, "%s: %s: backup mount point missing\n", progname, pcMtPoint); iExitCode = EX_NOINPUT; } fp = popen("sort |awk 'BEGIN { LAST=\" \"; FROM=\"none\";} $1 == LAST { print $2 \" and \" FROM \" both use \" $1; } /./ { LAST=$1; FROM=$2; }'", "w"); if ((FILE *)0 == fp) { fprintf(stderr, "%s: popen: %s\n", progname, strerror(errno)); iExitCode = EX_SOFTWARE; return 4; } for (iScan = iRet = 0; iScan < iAlts; ++iScan) { MetaRep(fp, ppBPCheck[iScan]); if ((SBP_ENTRY *)0 != ppBPCheck[iScan]->pBPswap) { MetaRep(fp, ppBPCheck[iScan]->pBPswap); } } /* include backup-less fs's and the unloved devices, if we can */ for (pBPPrim = pSBPPrimary; (SBP_ENTRY *)0 != pBPPrim; pBPPrim = pBPPrim->pBPnext) { if (0 != (pBPPrim->wflags & WORK_REPCHECK)) continue; if ((SBP_ENTRY *)0 != pBPPrim->pBPswap) continue; /* ".", "swap", "procfs", "proc", "md" and the like */ if ('/' != pBPPrim->pcdev[0]) continue; MetaRep(fp, pBPPrim); } for (pBPPrim = pSBPIgnore; (SBP_ENTRY *)0 != pBPPrim; pBPPrim = pBPPrim->pBPnext) { register char *pcBlock, *pcEos, c; if (0 != (pBPPrim->wflags & WORK_REPCHECK)) continue; pBPPrim->wflags |= WORK_REPCHECK; for (pcBlock = pBPPrim->pcdev; isspace(*pcBlock); ++pcBlock) /* nada */; if ((char *)0 != strchr(pcBlock, ':')) continue; for (pcEos = pcBlock; '\000' != *pcEos; ++pcEos) { if (isspace(*pcEos)) break; } c = *pcEos; *pcEos = '\000'; if ('-' != pcBlock[0] && '\000' != pcBlock[1]) { fprintf(fp, "%s %s(%d)\n", pcBlock, pBPPrim->pcmount, pBPPrim->iline); } *pcEos = c; } pclose(fp); fSawSlash = 0; for (iScan = iRet = 0; iScan < iAlts; ++iScan) { if ((SBP_ENTRY *)0 == (pBPPrim = ppBPCheck[iScan]->pBPswap)) continue; if (0 == strcmp("/", pBPPrim->pcmount)) { fSawSlash = 1; } pcTail = strrchr(pBPPrim->pcmount, '/'); if ((char *)0 == pcTail || pcTail == pBPPrim->pcmount) continue; switch (IsMountPt(pBPPrim->pcmount)) { case -1: break; case 1: continue; case 0: fprintf(stderr, "%s: %s(%d): not presently mounted\n", progname, pBPPrim->pcmount, pBPPrim->iline); iExitCode = EX_NOINPUT; continue; } fprintf(stderr, "%s: stat: %s: %s\n", progname, pBPPrim->pcmount, strerror(errno)); iExitCode = EX_NOINPUT; } for (iScan = iRet = 0; iScan < iAlts; ++iScan) { MetaMissing(ppBPCheck[iScan]->pBPswap->pcmount); } if (!fSawSlash) { fprintf(stderr, "%s: no backup for the root filesystem\n", progname); iExitCode = EX_DATAERR; } /* Check the paired partitions * then the lonely ones * then the devices, if they are marked with pass numbers */ for (iScan = iRet = 0; iScan < iAlts; ++iScan) { if ((SBP_ENTRY *)0 == (pBPPrim = ppBPCheck[iScan]->pBPswap)) continue; SplitOpts(apcPrim, strdup(pBPPrim->pcrest)); SplitOpts(apcAlt, strdup(ppBPCheck[iScan]->pcrest)); MetaType(pBPPrim, apcPrim[0], apcAlt[0]); if (isdigit(apcPrim[2][0])) { /* dump frequ, ignore me */ } else { MetaAtBoot(pBPPrim, apcPrim[2], apcAlt[2]); } if (isdigit(apcPrim[1][0])) { MetaPass(pBPPrim, apcPrim[1], apcAlt[1], 1); MetaOpts(pBPPrim, apcPrim[3], apcAlt[3]); } else { MetaPass(pBPPrim, apcPrim[3], apcAlt[3], 3); MetaOpts(pBPPrim, apcPrim[1], apcAlt[1]); } pBPPrim->wflags |= WORK_PASSCHECK; } for (pBPPrim = pSBPPrimary; (SBP_ENTRY *)0 != pBPPrim; pBPPrim = pBPPrim->pBPnext) { if (0 != (pBPPrim->wflags & WORK_PASSCHECK)) continue; pBPPrim->wflags |= WORK_PASSCHECK; SplitOpts(apcPrim, strdup(pBPPrim->pcrest)); /* Don't check mount options on lonely partitions */ if (isdigit(apcPrim[1][0])) { MetaPass(pBPPrim, apcPrim[1], (char *)0, 1); } else { MetaPass(pBPPrim, apcPrim[3], (char *)0, 3); } } for (pBPPrim = pSBPIgnore; (SBP_ENTRY *)0 != pBPPrim; pBPPrim = pBPPrim->pBPnext) { if (0 != (pBPPrim->wflags & WORK_PASSCHECK)) continue; pBPPrim->wflags |= WORK_PASSCHECK; SplitOpts(apcPrim, strdup(pBPPrim->pcrest)); if (-1 != HasOpt("swap,nfs,auto,.", apcPrim[0])) { continue; } if (isdigit(apcPrim[1][0])) { MetaPass(pBPPrim, apcPrim[1], (char *)0, 1); } else { MetaPass(pBPPrim, apcPrim[3], (char *)0, 3); } } return 0; } /* Leave the bad boy on-line for the super-user (ksb) */ static int LeaveMounted(pBP) SBP_ENTRY *pBP; { fUpdateFstab = 0; return 0; } /* build the exchanged vfstab and install it (ksb) */ static int BackupFstab(int iAlts, SBP_ENTRY **ppBPOrder) { auto char acInstCmd[MAXPATHLEN+4]; register FILE *fpInstall; /* When we didn't copy any partitios, or were told not to, * or didn't copy the root partition, don't update fstab. */ if (0 == iAlts || !fUpdateFstab) { return 0; } if (0 != strcmp(pcMtPoint, ppBPOrder[0]->pcmount)) { return 0; } sprintf(acInstCmd, "%s -q - %s%s", acInstallPath, pcMtPoint, acFstabPath); if (fVerbose) { printf("%s <<\\!\n", acInstCmd); DumpFs(stdout); printf("!\n"); } if (fExec) { fpInstall = popen(acInstCmd, "w"); DumpFs(fpInstall); pclose(fpInstall); } return 0; } /* What we know how to do, first is default. */ static SBP_METHOD aSMList[10] = { { "dump", "use filesystem dump and restore to copy partitions", CreateMkTable, CopyDumpRestore, BackupFstab, UmountPartition, ParamDump, "[dump-keys params]"}, #if HAVE_FSSNAP { "fssnap","use fssnap with dump and restore to copy partitions", CreateMkTable, CopyFsSnap, BackupFstab, UmountPartition, ParamDump, "[fssnap options]"}, #endif { "cpio", "use cpio to copy partitions", CreateMkTable, CopyCpio, BackupFstab, UmountPartition, ParamDump, "[cpio-switches params]"}, { "dd", "try to copy the block device with dd", CreateNada, CopyDD, BackupFstab, UmountPartition, ParamDash, "[dd options]"}, { "tar", "copy the partition data with GNU tar", CreateMkTable, CopyTar, BackupFstab, UmountPartition, ParamDash, "[tar options]"}, {"rsync", "use the rsync tool to sync the partitions", CreateNada, CopyRsync, BackupFstab, UmountPartition, ParamDash, "[rsync options]"}, { "mk", "copy the partition with mk markup from the filesystem table", CreateMkTable, CopyMk, BackupFstab, UmountPartition, ParamDash, "[mk-opts]"}, {"mount", "just mount alternate filesystems", CreateMkTable, BlankFs, (int (*)())0, LeaveMounted, ParamDash, "[empty|fsck]"}, { "sane", "just check the configuration for sanity", CreateNada, BlankFs, MetaCheck, UmountPartition, ParamDash, "[empty|force|fsck|mount|strict]"}, {"unmount", "just umount alternate filesystems", CreateNada, (int (*)())0, (int (*)())0, UmountPartition, ParamDash, "[force]"}, {(char *)0, "ksb's credit line. Copyright K.S.Braunsdorf 1999-2012. Released under the BSD 2-clause License", (void (*)())0, (int (*)())0, (int (*)())0, (int (*)())0, (void (*)())0} }; /* Explain things to the SA trying to use us (ksb) */ void MethHelp(fp) FILE *fp; { register int i, iLen, iTemp; register char *pcMeth; iLen = 0; for (i = 0; (char *)0 != (pcMeth = aSMList[i].pcmeth); ++i) { iTemp = strlen(pcMeth); if (iTemp > iLen) iLen = iTemp; } iLen += 1; for (i = 0; (char *)0 != (pcMeth = aSMList[i].pcmeth); ++i) { fprintf(fp, "%-*s %s\n", iLen, pcMeth, aSMList[i].pchelp); } fflush(fp); } static SBP_METHOD *pSMUse = aSMList; void SetHow(pcHow) char *pcHow; { register char *pcMeth, *pcColon; register int i, iLen; if ('?' == pcHow[0]) { MethHelp(stdout); exit(EX_OK); } if ((char *)0 != (pcColon = strchr(pcHow, ':'))) { iLen = pcColon - pcHow; ++pcColon; } else { iLen = strlen(pcHow); } for (i = 0; (char *)0 != (pcMeth = aSMList[i].pcmeth); ++i) { if (0 == strncmp(pcMeth, pcHow, iLen)) { pSMUse = & aSMList[i]; if ((char *)0 != pcColon) { (pSMUse->pfiparams)(pcColon, pSMUse); } return; } } fprintf(stderr, "%s: %s: unknown sync method, try `?' for a list\n", progname, pcHow); exit(EX_USAGE); } /* count the number of this character in the string (ksb) */ static int CountEm(pcStr, cThing) char *pcStr; int cThing; { register int iRet; for (iRet = 0; '\000' != *pcStr; ++pcStr) { if (cThing == *pcStr) ++iRet; } return iRet; } /* the mount points with fewer slashes and shorter names go first (ksb) */ static int PartByLen(ppBPLeft, ppBPRight) const SBP_ENTRY **ppBPLeft, **ppBPRight; { register char *pcLeft, *pcRight; register int iCmp; pcLeft = ppBPLeft[0]->pcmount; pcRight = ppBPRight[0]->pcmount; iCmp = CountEm(pcLeft, '/') - CountEm(pcRight, '/'); if (0 != iCmp) return iCmp; iCmp = strlen(pcLeft) - strlen(pcRight); if (0 != iCmp) return iCmp; return strcmp(pcLeft, pcRight); } /* remove the trailing slash that zsh/bash/ksh file completion (ksb) * puts on a parameter */ static void TrimTrailing(pcPath) char *pcPath; { register int i; for (i = strlen(pcPath); i-- > 0; pcPath[i] = '\000') { if ('/' != pcPath[i]) return; } } /* sync the partitions in the list, either primary or backup (ksb) * by default (zero listed) sync all */ void DoSync(argc, argv, pBPList) int argc; char **argv; SBP_ENTRY *pBPList; { register int i, iMtLen, iErr, iTrip; register SBP_ENTRY *pBPScan; register char *pcMatch; iErr = 0; iMtLen = strlen(pcMtPoint); /* We remove trailing slash's on pcMatch (or ignore them) */ for (i = 0; i < argc; ++i) { register char *pcCand; if (0 == strncmp(argv[i], pcMtPoint, iMtLen)) { pcMatch = argv[i] + iMtLen; if ('/' == *pcMatch) { ++pcMatch; } } else { pcMatch = argv[i] + 1; } TrimTrailing(pcMatch); for (pBPScan = pBPList; (SBP_ENTRY *)0 != pBPScan; pBPScan = pBPScan->pBPnext) { pcCand = pBPScan->pcmount+iMtLen; if ('/' == *pcCand) { ++pcCand; } if (0 == strcmp(pcCand, pcMatch)) break; } if ((SBP_ENTRY *)0 != pBPScan) { pBPScan->wflags |= WORK_TODO; continue; } fprintf(stderr, "%s: %s : can't find backup partition\n", progname, argv[i], pcMatch); iErr = 1; } if (0 != iErr) { exit(EX_UNAVAILABLE); } iTrip = 0; for (pBPScan = pBPList; (SBP_ENTRY *)0 != pBPScan; pBPScan = pBPScan->pBPnext) { if (0 != argc && 0 == (pBPScan->wflags & WORK_TODO)) { continue; } ++iTrip; } /* nothing to sync in fstable, I guess */ if (0 == iTrip) { if (fVerbose) { printf("%s: %s: nothing to sync\n", progname, pcFstab); } return; } /* find the order to do them in, sigh */ ppBPOrder = (SBP_ENTRY **)calloc((iTrip|7)+1, sizeof(SBP_ENTRY *)); if ((SBP_ENTRY **)0 == ppBPOrder) { fprintf(stderr, "%s: calloc: %d,%d: %s\n", progname, (iTrip|7)+1, sizeof(SBP_ENTRY *), strerror(errno)); exit(EX_OSERR); } iTrip = 0; for (pBPScan = pBPList; (SBP_ENTRY *)0 != pBPScan; pBPScan = pBPScan->pBPnext) { if (0 != argc && 0 == (pBPScan->wflags & WORK_TODO)) { continue; } ppBPOrder[iTrip++] = pBPScan; } ppBPOrder[iTrip] = (SBP_ENTRY *)0; qsort((void *)ppBPOrder, iTrip, sizeof(SBP_ENTRY *), PartByLen); /* hook for backup method to build any files it needs */ (pSMUse->pficreate)(ppBPOrder, iTrip); /* unmount backups, we may have been interrupted */ for (i = 0; i < iTrip; ++i) { (pSMUse->pfiumount)(ppBPOrder[iTrip - 1 - i]); } /* one might label the disk here */ if ((char *)0 != pcXapply) { for (i = 0; i < iTrip; ++i) { if (0 != Run(pcXapply, ppBPOrder[i])) { return; } } } if (iTrip > 0 && (char *)0 != pcLabel && 0 != Run(pcLabel, ppBPOrder[0])) { return; } /* Make sure the top level mount-point exists * if one just asks for /usr/local to be sync'd we'll * make a /backup/usr/local mount point to sync it on, * but the extra dirs don't hurt anyone. * dd -> "dd if=%Xd of=%d" (in another routine, this is mounted) */ for (i = 0; i < iTrip; ++i) { if (fCheckSource && 1 != IsMountPt(ppBPOrder[i]->pBPswap->pcmount)) { fprintf(stderr, "%s: %s: not presently mounted -- skipped. Use -K to force copy.\n", progname, ppBPOrder[i]->pBPswap->pcmount); iExitCode = EX_UNAVAILABLE; continue; } if ((char *)0 != pcBefore && 0 != Run(pcBefore, ppBPOrder[i])) { break; } if ((int (*)())0 == pSMUse->pficopy) { continue; } if (0 != (pSMUse->pficopy)(ppBPOrder[i])) { break; } } /* When we did "/", (which must be the first on in the list) then * we need to install a new /backup/etc/fstab with the partitions * swapped around, unless they told us not to -- ksb */ if ((int (*)())0 != pSMUse->pfifinish) { (pSMUse->pfifinish)(i, ppBPOrder); } /* Trap a command to fix stuff before we go, * this is the place to run "installboot" or the like. */ if (i > 0 && (char *)0 != pcCleanup) { (void)Run(pcCleanup, ppBPOrder[0]); } while (i-- > 0) { if ((char *)0 != pcAfter && 0 != Run(pcAfter, ppBPOrder[i])) { break; } (pSMUse->pfiumount)(ppBPOrder[i]); } /* old habits die hard */ sync(); }