#!mkcmd # $Id: rcsvg.m,v 2.17 2005/12/19 15:48:47 ksb Exp $ # # command line options for RCS rev grab -- a snapshot program # for a product kept under RCS (ksb@cc.purdue.edu) # # N.B.: `version' refers to the product `revision' refers # to any single source file that composes the product. from '' from '' from '' from '' from '' from '"machine.h"' from '"util.h"' from '"scandir.h"' require "util_errno.m" require "std_help.m" "std_version.m" "std_control.m" basename "rcsvg" "" getenv "RCSVG" %i static char rcsid[] = "$Id: rcsvg.m,v 2.17 2005/12/19 15:48:47 ksb Exp $"; %% integer variable "iUmask" { before '%n = umask(022);umask(%n);' } char* variable "pcVersion" { param "version" help "the version of the product we want to stage" } left "pcVersion" { update "/* got them */" abort 'fprintf(stderr, "%%s: must provide a version string\\n", %b);' } char* 'S' { named "pcStage" param "stage" help "the staging area we should construct/update" } boolean 'R' { named "fLinkRCS" help "build a symbolic link back to each RCS directory" } boolean 'm' { named "fMode" help "take modes from the RCS delta files for destination" } boolean 'f' { named "fUnlink" help "forces delete of out of sync files before checkout" } boolean 'd' { named "fByDate" help "take the version as an rcs -d from date" } after { named "PostInit" update "if (%rRn) {%n();}" } zero { update '%Ren(".");' } every { param "source" named "CopyOut" help "a top-level source directory for the product" } # bit mask of type of failures found # 1 RCS/file,v not readable # 2 found a character or block special file # 8 found a trash file in RCS # integer variable "iRet" { init "0" } boolean 'c' { named "fCmp" help "do not change files which compare to previously staged files" } exit { update "exit(iRet);" } from '"machine.h"' %i static char acSource[MAXPATHLEN+1], acStage[MAXPATHLEN+1]; static char acLinkPrefix[MAXPATHLEN+1], *pcLinkPrefix = (char *)0; %% %c /* make sure we can lay hands on the path to the source for the RCS (ksb) * links back to the source under -R. * If ksh (or any parent) provides a "better" name for the pwd then * we should take that path. */ void PostInit() { register char *pcPwd; auto struct stat stSys, stEnv; extern char *getenv(); if ((char *)0 == getcwd(acLinkPrefix, sizeof(acLinkPrefix))) { fprintf(stderr, "%s: getcwd: %s\n", progname, strerror(errno)); exit(1); } if ((char *)0 != (pcPwd = getenv("PWD")) && strlen(pcPwd) < strlen(acLinkPrefix) && -1 != stat(pcPwd, & stEnv) && -1 != stat(acLinkPrefix, & stSys)) { if (stSys.st_ino == stEnv.st_ino && stSys.st_dev == stEnv.st_dev) { (void)strcpy(acLinkPrefix, pcPwd); } } pcLinkPrefix = acLinkPrefix+strlen(acLinkPrefix); } /* In this alphasorted list of (struct direct *) [some nil] find (ksb) * (via a binary search) the named file... return a pointer to * the entry so we may remove it in the caller * * This allows the caller to `shoot' the return array from scandir * a few files at a time and still have a list of the files left. */ static struct DIRENT ** FindDEnt(pcName, ppDE, n) char *pcName; struct DIRENT **ppDE; int n; { register int i, mid; register int cmp; if (0 == n) return (struct DIRENT **)0; for (i = 0; i < n; ++i) { if ((struct DIRENT *)0 != ppDE[i]) break; } if (n == i) { return (struct DIRENT **)0; } if (0 == strcmp(pcName, (ppDE[i])->d_name)) { return & ppDE[i]; } do { --n; if ((struct DIRENT *)0 != ppDE[n]) break; } while (n > i); if (0 == strcmp(pcName, (ppDE[n])->d_name)) { return & ppDE[n]; } while (i != n) { mid = (n+i)/2; while (mid < n && (struct DIRENT *)0 == ppDE[mid]) ++mid; if (mid == n) { mid = (n+i)/2; while (mid > i && (struct DIRENT *)0 == ppDE[mid]) --mid; if (mid == i) break; } cmp = strcmp(pcName, (ppDE[mid])->d_name); if (cmp == 0) { return & ppDE[mid]; } if (cmp < 0) { n = mid; } else { i = mid; } if (i+1 == n) break; } return (struct DIRENT **)0; } /* blow off ., and .. (ksb) */ int TlSelect(pDE) struct DIRENT *pDE; { return !('.' == pDE->d_name[0] && ('\000' == pDE->d_name[1] || ('.' == pDE->d_name[1] && '\000' == pDE->d_name[2]))); } /* scan a top level directory for sub directories and RCS caches (ksb) * take apropos action in each case (skip . & .., of course) * * check to make sure the source directory exists and has /RCS in it. * make the stage area * scan the files in the source dir and dispatch them */ void TopLevel(pcSTail, pcDTail, pcLinkDest) char *pcSTail, *pcDTail, *pcLinkDest; { register struct DIRENT *pDE; register int i; register char *pcRTail; register char *pcComma; register struct DIRENT **ppDEDink; auto int nO, nV, iLen; auto struct DIRENT **ppDEOut, **ppDEVees; auto char acRCScv[MAXPATHLEN+10]; auto struct stat stThis, stComVee; auto char acLink[MAXPATHLEN+1]; auto int fSawCache, fMadeRCS; extern int alphasort(); *pcSTail = '\000'; if (0 != VrfyDir(acSource, 0)) { return ; } (void)strcpy(acRCScv, acSource); pcRTail = acRCScv + strlen(acRCScv); *pcRTail++ = '/'; *pcRTail++ = 'R'; *pcRTail++ = 'C'; *pcRTail++ = 'S'; *pcRTail = '\000'; if (0 != VrfyDir(acRCScv, 0)) { fprintf(stderr, "%s: %s has no RCS directory [%s] (warning only)\n", progname, acSource, acRCScv); nV = 0; } else if (-1 == (nV = scandir(acRCScv, &ppDEVees, TlSelect, alphasort))) { fprintf(stderr, "%s: scandir: %s: %s\n", progname, acSource, strerror(errno)); return; } *pcDTail = '\000'; if (0 != VrfyDir(acStage, 1)) { return; } if (-1 == (nO = scandir(acSource, &ppDEOut, TlSelect, alphasort))) { fprintf(stderr, "%s: scandir: %s: %s\n", progname, acSource, strerror(errno)); return; } *pcDTail++ = '/'; *pcSTail++ = '/'; *pcRTail++ = '/'; /* Looking in the RCS directory first: * look for non-plain files * look for non-,v files * look for file,v we can't read * loof for "RCS,v" which is an artifact of "rcs -i *", ouch! * look for ,semaphore files * then copy the son of a gun to the stage area. */ for (i = 0; i < nV; ++i) { (void)strcpy(pcRTail, ppDEVees[i]->d_name); if (-1 == stat(acRCScv, &stThis)) { fprintf(stderr, "%s: stat: %s: %s\n", progname, acSource, strerror(errno)); continue; } switch (stThis.st_mode & S_IFMT) { case 0: case S_IFREG: /* handled below */ break; case S_IFDIR: fprintf(stderr, "%s: %s is a subdirectory of an RCS cache directory (ignored)\n", progname, acRCScv); iRet |= 1; continue; default: fprintf(stderr, "%s: %s delta file with funny type\n", progname, acRCScv); iRet |= 2; continue; } if ((char *)0 == (pcComma = strrchr(ppDEVees[i]->d_name, ','))) { fprintf(stderr, "%s: %s should not live under a RCS directory (no ,v extender)\n", progname, acRCScv); iRet |= 8; continue; } if ('v' != pcComma[1] && '\000' != pcComma[2]) { fprintf(stderr, "%s: %s should not live under a RCS directory (extender \"%s\")\n", progname, acRCScv, pcComma); iRet |= 8; continue; } if (0 != access(acRCScv, R_OK)) { fprintf(stderr, "%s: %s cache file is not readable!\n", progname, acRCScv); iRet |= 1; continue; } /* is not -R we don't complain, I guess that's OK -- ksb */ if (fLinkRCS && 0 == strcmp("RCS,v", ppDEVees[i]->d_name)) { fprintf(stderr, "%s: %s cache file would clobber RCS backlink\n", progname, acRCScv); iRet |= 17; } /* file is a ,semaphore file if RCS/file,v also exists * I think some versions of RCS must do this with dots. */ if (',' == ppDEVees[i]->d_name[0]) { fprintf(stderr, "%s: %s might be a semaphore file!\n", progname, acRCScv); } /* find the file in acSource list and rm it */ *pcComma = '\000'; (void)strcpy(pcDTail, ppDEVees[i]->d_name); ppDEDink = FindDEnt(ppDEVees[i]->d_name, ppDEOut, nO); *pcComma = ','; if ((struct DIRENT **)0 != ppDEDink) { *ppDEDink = (struct DIRENT *)0; } Extract(acRCScv, acStage, 07777 & stThis.st_mode); } /* OK look through the files left after we skimmed off the * RCS delta files and check for * comma-v files not under RCS (Geeze) * RCS dir to ignore * sub dirs to look in * symbolic links to copy [see ExtLink] */ fMadeRCS = fSawCache = 0; pcRTail -= 4; for (i = 0; i < nO; ++i) { if ((struct DIRENT *)0 == ppDEOut[i]) { continue; } /* if this is an RCS delta file at the wrong level... */ if ((char *)0 != (pcComma = strrchr(ppDEOut[i]->d_name, ',')) && 'v' == pcComma[1] && '\000' == pcComma[2]) { *pcRTail = '\000'; *pcComma = '\000'; (void)strcpy(pcDTail, ppDEOut[i]->d_name); *pcComma = ','; } else { pcComma = (char *)0; (void)sprintf(pcRTail, "%s,v", ppDEOut[i]->d_name); (void)strcpy(pcDTail, ppDEOut[i]->d_name); } (void)strcpy(pcSTail, ppDEOut[i]->d_name); if (-1 == lstat(acSource, &stThis)) { fprintf(stderr, "%s: stat: %s: %s\n", progname, acSource, strerror(errno)); continue; } switch (stThis.st_mode & S_IFMT) { register char *pcLnText; auto char acLCS[MAXPATHLEN+2]; case 0: case S_IFREG: /* handled below */ break; case S_IFDIR: /* RCS dir is extracted by us, do not decend */ if (0 != strcmp("RCS", ppDEOut[i]->d_name)) { auto char acLinkDest[MAXPATHLEN+4]; sprintf(acLinkDest, "%s%s", '/' == acSource[0] ? "" : "../", pcLinkDest); TopLevel(pcSTail+strlen(pcSTail), pcDTail+strlen(pcDTail), acLinkDest); continue; } if (!fLinkRCS) { continue; } (void)strcpy(pcDTail, "RCS"); *--pcRTail = '\000'; if ('.' == acRCScv[0] && ('\000' == acRCScv[1] || '/' == acRCScv[1])) { (void)strcpy(pcLinkPrefix, acRCScv+1); pcLnText = acLinkPrefix; } else { pcLnText = acRCScv; } sprintf(acLCS, "%s%s/RCS", pcLinkDest, pcLnText); /* ZZZ read existing link? */ if (fVerbose) { printf("ln -s %s %s\n", acLCS, acStage); } if (fExec && -1 == symlink(acLCS, acStage)) { fprintf(stderr, "%s: symlink: %s to %s: %s\n", progname, acLCS, acStage, strerror(errno)); } *pcRTail++ = '/'; fMadeRCS = 1; continue; case S_IFLNK: if ((char *)0 != pcComma) { fprintf(stderr, "%s: %s is a link to a delta file at the wrong level, save us all\n", progname, acSource); continue; } /* copy the link but check for leading '/' * and '../..' out of scope */ /* link case */ iLen = readlink(acSource, acLink, MAXPATHLEN); if (-1 == iLen) { fprintf(stderr, "%s: readlink: %s: %s\n", progname, acSource, strerror(errno)); iRet |= 16; continue; } acLink[iLen] = '\000'; ExtLink(acLink, acStage); continue; default: fprintf(stderr, "%s: %s ignored due to funny node type\n", progname, acSource); continue; } /* plain file case, might be a "file,v" or a "file" */ /* We found a file,v maybe there is a co'd one as well. * We'll extract the file,v and ignore any co'd copy. */ if ((char *)0 != pcComma) { if (fLinkRCS && 0 == strcmp("RCS,v", ppDEOut[i]->d_name)) { fprintf(stderr, "%s: %s cache file would clobber RCS backlink\n", progname, acSource); iRet |= 17; continue; } Extract(acSource, acStage, 07777 & stThis.st_mode); *pcComma = '\000'; ppDEDink = FindDEnt(ppDEOut[i]->d_name, ppDEOut, nO); *pcComma = ','; if ((struct DIRENT **)0 == ppDEDink) { *ppDEDink = (struct DIRENT *)0; } fSawCache = 1; continue; } /* the other order, we found "file" look for "file,v" */ ppDEDink = FindDEnt(pcRTail, ppDEOut, nO); if ((struct DIRENT **)0 == ppDEDink) { fprintf(stderr, "%s: %s ignored (no RCS cache)\n", progname, acSource); continue; } *ppDEDink = (struct DIRENT *)0; Extract(acRCScv, acStage, 07777 & stThis.st_mode); fSawCache = 1; continue; } /* We need an RCS back link and saw a cache file in . and * didn't see an RCS directory. Make RCS@->here --ksb * If you have a mix of cached,v in . and RCS you loose (can * only see the RCS ones). * [NO! I'm not putting links in a real directory for you.] */ *--pcSTail = '\000'; if (fLinkRCS && fSawCache && !fMadeRCS) { auto char acLCS[MAXPATHLEN+2]; *pcLinkPrefix = '\000'; (void)strcpy(pcDTail, "RCS"); if (fVerbose) { printf("ln -s %s %s\n", acLinkPrefix, acStage); } if (fExec && -1 == symlink(acLinkPrefix, acStage)) { fprintf(stderr, "%s: symlink: %s to %s: %s\n", progname, acLinkPrefix, acStage, strerror(errno)); } } *--pcDTail = '\000'; } /* start the whole thing off in a top level directory (ksb) */ void CopyOut(pcSource) char *pcSource; { if ((char *)0 == pcStage) { (void)sprintf(acStage, "/tmp/%s", pcVersion); } else { (void)strcpy(acStage, pcStage); } (void)strcpy(acSource, pcSource); TopLevel(acSource+strlen(acSource), acStage+strlen(acStage), ""); } %%