Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1515 lines
43 KiB

// Scripting
//
// It would be dangerous for SLM to directly manipulate users' and system
// files, for if the system crashed they could be left in an incomplete or
// inconsistent state (California). Instead, all file operations are first
// done to temporary files, and the final installation action for these files
// are recorded in a "script." Each line of the script describes one file
// operation, and when the script is closed (before it is run) a final
// important "exit" line is written to the end. Later this line may be
// tested to determine if a crashed program's script should be rerun.
//
// When an SLM command completes its operations in some directory, it "runs"
// the accumulated script. At this point all of the changes are stored
// in temp files on disk, so that if SLM were to suddenly terminate, the
// scripted actions could be rerun by a subsequent "sadmin runscript". Then
// each line of the script is executed, resulting in the temporary files being
// moved, linked, deleted, etc. When every script operation line has been
// run, the script is removed.
//
// If SLM must terminate prematurely, due to some fatal error or user interrupt,
// we test if the script has been closed. If not, no irreversible actions have
// taken place and the script can be safely aborted, removing whatever temp
// files were created. However, if a fatal error occurs while running the
// script, we have a serious problem, because we may have already installed
// some of the temp files, and thus changed the user's files; therefore
// scripted operations must not be capable of failure.
//
// So except for any bugs still lurking in SLM, it is not possible to leave a
// script file behind. However, impatient users do occasionally reboot their
// machines while SLM is running. Even this condition is not serious. The
// next time someone runs an SLM command, it will stall trying to lock the
// status file. Eventually they will run "sadmin unlock", which first checks
// for the presence of any script files and tries to run or abort them.
//
// Recall that we can tell if a script is complete if it concludes with an
// "exit" line. If it doesn't, then no script operation could have been run
// (they are only run after the "exit" line is written to disk), and so it
// is safe to "abort" the status file. Each scripted operation is undone
// (for example, for scripted operation "rename a to b", we remove the temp
// file "a".)
//
// On the other hand, it may be that a script file does end with "exit". In
// this case we must assume that some of the scripted operations were executed,
// having an irreversible effect on the the project or user's files. In this
// case we must try to rerun each so in the script, skipping those so's which
// have already been executed. (For instance, if the script operation was
// "rename a to b" and if a doesn't exist, we conclude the so has already been
// executed.
//
// This rerunning or aborting seems quite straightforward. Unfortunately
// there is a complication. The user running "sadmin unlock" may not be the
// user who left the script locked in the first place. Some so's will refer
// to project files, and some to local files. In general there is no way for
// the second user to access files on the first user's machine, and so the
// rerun would fail and the script would still be locked.
//
// To avoid getting stuck in this situation, we actually build two script
// files, one for local operations (affecing only files on the user's machine)
// and one for global operations. We collectively call these files "the
// script.") The local script file is kept in the user's local directory,
// the global script file in the project's corresponding etc directory.
//
// More complications. If you read the "locking" documentation in stfile.h,
// you will see there are three kinds of locking: lckAll, lckEd, and lckNil,
// corresponding to operations which may modify the entire status file, a
// single enlisted directory, or none of the file, respectively. In the lckEd
// and lckNil cases, each enlisted user can be simultaneously executing an
// SLM program requiring script files. Clearly we cannot use a single fixed
// name for the global script files.
//
// Instead, we use the following system: the global script files are called
// "<ied>.scr" (lckEd) or "all.scr" (lckAll), and the local files are called
// "local.scr". When FInitScript is called to initiate scripting, we first
// check that no conflicting script files already exist. If they do, we do
// not create the script but instead instruct the user to "sadmin runscript":
//
// sh.lck Global script Local script Conflicts with
//
// lckNil never local.scr local.scr
// lckEd <ied>.scr local.scr local.scr, all.scr, <ied>.scr
// lckAll all.scr local.scr local.scr, all.scr, <any-ied>.scr
//
//
// Status file interactions
//
// Scripting interacts intimately with status file locking. We have already
// discussed locking levels. The status file is locked by FLoadStatus, but
// is *not* unlocked by FlushStatus. In the normal case, the status file is
// automatically unlocked by the last script operation in the global script
// file (either soInstall1Ed or soInstall). (In the case of an abort or error,
// the status file is explicitly unlocked by AbortStatus.)
//
// soInstall move the old status file to "status.bak"; a temp file contains
// the new status file (already "unlocked"); rename this temp
// file to "status.slm".
//
// soInstall1Ed overwrite parts of the status file with the new ed (stored in
// a temp file) and unlock the status file if no other concurrent
// ssyncs are still running. Unlike the soInstall action, this
// isn't atomic and if a crash occurs, the status file can be
// left inconsistent (but not seriously).
//
// API
//
// FInitScript(pad, lck)
// Usually called from FLoadStatus, after loading status file, but
// this is not essential. If lck == lckEd, then pad->iedCur != iedNil.
// Builds local and possibly global scripts.
// RunScript()
// Run whatever scripts were created by last call to FInitScript. OK to
// call RunScript with no runnable scripts.
// AbortScript()
// Abort whatever scripts were created by last call to FInitScript. OK to
// call RunScript with no runnable scripts.
// FDoAllScripts(pad, lck, fPrompt, fPrintScripts)
// Rerun or abort any leftover scripts which conflict with the given lock.
// If fPrompt, query the user before processing the scripts. If
// fPrintScripts, print all performed operations.
#include "precomp.h"
#pragma hdrstop
#include "messages.h"
EnableAssert
typedef int DOSC; // how to DO the SCript
#define doscRun 0
#define doscRerun 1
#define doscAbort 2
#define ctryAuto 5
#define ctryMax 200
private F FOpenFx(AD *, FX);
private void CloseFx(FX, DOSC);
private void UnlkFx(FX);
private F FRunFx(FX);
private NE *PneScripts(AD *, LCK, IED);
private F FHasExit(PTH *);
private void DoFx(FX, DOSC);
private F FDoScript(PTH *, DOSC, F);
private void PrintPercentDone(int, int, char *, DOSC);
private F FUnDoneSo(SO, char *, char *, char *);
private F FRunSo(SO, char *, char *, char *);
private void AbortSo(SO, char *);
private void PrintSo(SO, char *, char *);
private F FRenSz(char *, char *);
private F FMakeRO(char *, F);
private F FAppendSz(char *, char *);
private F FDeleteSz(char *);
private void InitNextSo(char [], char **);
private F FNextSo(SO *, char **, char **, int, char [], char **);
private void ProcessScript(char *, DOSC);
F CarriageReturn(void);
typedef struct
{
int fd;
PTH pth[cchPthMax];
} SCR;
static SCR mpfxscr[fxMax] =
{
{ fdNil, "" }, // fxNil entry not used
{ fdNil, "" },
{ fdNil, "" }
};
#define FValidFx(fx) ((fx) == fxLocal || (fx) == fxGlobal)
#define FOpenedFx(fx) (mpfxscr[(fx)].fd >= 0)
static char *mpfxsz[] = { "nil", "local", "global" };
static const char szErrorClose[] =
"Cannot close the %s script. This may be caused by\n"
"lack of disk space (or other trouble) on the server or workstation.\n";
static const char szErrorEmpty[] =
"The %s script has been truncated. This may be caused by\n"
"lack of disk space (or other trouble) on the server or workstation.\n";
F
FClnScript()
{
return !(FOpenedFx(fxLocal) || FOpenedFx(fxGlobal));
}
// Try to create a new script. Since we may be here with an unlocked status
// file (for example, creating a local script for catsrc temp files), we must
// be careful to atomically "test and set" if the script file already exists
// to avoid races.
private F
FOpenFx(
AD *pad,
FX fx)
{
char sz[cchPthMax];
int fd;
AssertF(FValidFx(fx));
AssertF(!FOpenedFx(fx));
AssertF(mpfxscr[fx].pth[0]);
SzPhysPath(sz, mpfxscr[fx].pth);
// UNIX: There is an old trick: if you creat a file mode 0440, you
// get a writable file descriptor to it. If somebody else comes along
// and tries to creat the same file, the creat fails because the file
// exists and is not writable.
//
// DOS: We now use DOS call 5B, which fails if the file already
// exists. Unfortunately it opens the file in compatibility mode,
// and so we must close and reopen it.
//
// OS/2: Creat calls DosOpen with action 0x10, which creates the file
// if it doesn't already exist. Again we close and reopen because
// there's no way to pass omReadWrite through creat parameters.
if ((fd = _creat(sz, 0660)) < 0 ||
_close(fd) != 0 ||
(fd = _open(sz, omReadWrite)) < 0)
{
Error("can't create script %s\n", mpfxscr[fx].pth);
return fFalse;
}
mpfxscr[fx].fd = fd;
AppendScript(fx, "rem %s run by %&I on %s (%s script)\n", szOp, pad,
SzTime(time((long *)0)), mpfxsz[fx]);
return fTrue;
}
static char szExitLn[] = "exit";
#define cchExitLn (sizeof(szExitLn) - 1)
private void
CloseFx(
FX fx,
DOSC dosc)
{
AssertF(FValidFx(fx));
AssertF(FOpenedFx(fx));
if (dosc == doscRun)
AppendScript(fx, "%s\n", szExitLn);
if (_close(mpfxscr[fx].fd) != 0)
{
mpfxscr[fx].fd = fdNil;
FatalError(szErrorClose, mpfxsz[fx]);
}
mpfxscr[fx].fd = fdNil;
}
private void
UnlkFx(
FX fx)
{
char sz[cchPthMax];
AssertF(FValidFx(fx));
AssertF(!FOpenedFx(fx));
if (_unlink(SzPhysPath(sz, mpfxscr[fx].pth)) != 0)
FatalError("can't unlink script %s\n", mpfxscr[fx].pth);
}
// Check that no conflicting scripts exist, then open a new script.
//
// Often called from FLoadStatus, when lck != lckNil; however it can be
// safely called even if the status file isn't loaded. If lck == lckEd,
// then pad->iedCur must not be iedNil.
F
FInitScript(
AD *pad,
LCK lck)
{
NE *pne;
PTH pthDir[cchPthMax];
struct _stat st;
AssertF(lck != lckEd || pad->iedCur != iedNil);
AssertF(!FOpenedFx(fxLocal));
AssertF(!FOpenedFx(fxGlobal));
// Check that no conflicting scripts exist.
// If there are leftover scripts, try to process them
if (!FDoAllScripts(pad, lck, fTrue, fFalse)) {
// Either a script operation failed, or the user answered
// no to the prompt. We can't perform the operation until
// these scripts are taken care of.
NE *pneScripts = PneScripts(pad, lck, pad->iedCur);
Error("the following scripts still exist:\n");
ForEachNe(pne, pneScripts)
PrErr("\t%s\n", SzOfNe(pne));
PrErr("run sadmin runscript -s %&S -p %&P/C to remove\n", pad, pad);
FreeNe(pneScripts);
return fFalse;
}
// Try to create the local script file in /U/Q; unfortunately it
// might not exist if the user is not enlisted; in that case,
// create it in /U.
SzPrint(pthDir, "%&/U/Q", pad);
SzPrint(mpfxscr[fxLocal].pth, FStatPth(pthDir, &st) ? "%&/U/Q/local.scr" : "%&/U/local.scr", pad);
if (!FOpenFx(pad, fxLocal)) {
Error("(can't run more than one SLM command concurrently - each command needs a unique local.scr file)\n");
return fFalse;
}
// Create a global script if lckEd or lckAll.
if (lck == lckEd || lck == lckAll) {
AssertF(lck != lckEd || pad->iedCur != iedNil);
SzPrint(mpfxscr[fxGlobal].pth, lck == lckAll ?
"%&/S/etc/P/C/all.scr" : "%&/S/etc/P/C/%d.scr",
pad, pad->iedCur);
if (!FOpenFx(pad, fxGlobal)) {
CloseFx(fxLocal, doscAbort);
UnlkFx(fxLocal);
return fFalse;
}
}
return fTrue;
}
// Return the paths to all script files which conflict with lck.
private NE *
PneScripts(
AD *pad,
LCK lck,
IED iedCur)
{
DE de;
FA fa;
PTH pthScript[cchPthMax];
char szScript[cchFileMax + 1];
struct _stat st;
NE *pneList = 0;
NE **ppneLast;
InitAppendNe(&ppneLast, &pneList);
// Check for local script, which may be in /U. or /U/Q.
if (FStatPth(SzPrint(pthScript, "%&/U/local.scr", pad), &st))
AppendNe(&ppneLast, PneNewNm(pthScript, CchOfPth(pthScript), faNormal));
if (pad->pthUSubDir[1] &&
FStatPth(SzPrint(pthScript, "%&/U/Q/local.scr", pad), &st))
AppendNe(&ppneLast, PneNewNm(pthScript, CchOfPth(pthScript), faNormal));
// ignore global and ied.scr scripts if not locking status file
if (lck == lckNil)
return pneList;
AssertF(lck == lckAll || lck == lckEd);
// Check for global scripts
OpenPatDir(&de, SzPrint(pthScript, "%&/S/etc/P/C/Z", pad, (char *)0),
"*.scr", faFiles);
while (FGetDirSz(&de, szScript, &fa)) {
int wIed;
char *pchDot = index(szScript, '.');
AssertF(pchDot != 0);
*pchDot = 0;
if (strcmp(szScript, "all") == 0 ||
strcmp(szScript, "ALL") == 0 ||
(PchGetW(szScript, &wIed) == pchDot && pchDot > szScript &&
(lck != lckEd || wIed == (int)iedCur)))
{
*pchDot = '.';
SzPrint(pthScript, "%&/S/etc/P/C/Z", pad, szScript);
AppendNe(&ppneLast, PneNewNm(pthScript, CchOfPth(pthScript), fa));
}
}
CloseDir(&de);
return pneList;
}
//VARARGS2
// Write line of script file
void
AppendScript(
FX fx,
char *sz,
...)
{
int cch, cchWritten;
char rgch[512];
char *pch = rgch;
va_list ap;
AssertF(FValidFx(fx));
AssertF(FOpenedFx(fx));
va_start(ap, sz);
VaSzPrint(rgch, sz, ap);
va_end(ap);
cch = strlen(rgch);
AssertF(cch < sizeof(rgch));
while (cch) {
cchWritten = WriteLpbCb(mpfxscr[fx].fd, pch, cch);
// write shouldn't ever return 0, but just in case...
if (cchWritten <= 0) {
if (WRetryError(eoWrite, "writing", 0, mpfxscr[fx].pth) != 0)
continue;
else
FatalError("Cannot append to the %s script. This may be "
"caused by\nlack of disk space (or other "
"trouble) on the server or workstation.\n",
mpfxsz[fx]);
}
ClearPreviousError();
cch -= cchWritten;
pch += cchWritten;
}
}
// Name: CbStatusCalculate
// Purpose: determine size that the status file should be
// Assumes: szFile is an SLM status file
// Returns: calculated size of status file, or (unsigned long)-1 if
// any error occured.
// Warning: there was a LanMan 2.1 bug that would occasionally reboot the
// machine if you opened a file that was already open. So maybe
// you won't want to use this if you have the file open.
private unsigned long
CbStatusCalculate(
char *szFile)
{
char szPhys[cchPthMax];
int hf;
SH sh;
if ((hf = _open(SzPhysPath(szPhys, szFile), omReadOnly)) == -1)
return ((unsigned long) -1);
if (_read(hf, &sh, sizeof(SH)) != sizeof(SH))
return ((unsigned long) -1);
_close(hf);
return ((POS)CbStatusFromPsh(&sh));
}
// Compare the filename part of a path to the filename in sz
// to see if they are the same (without regard to case).
#define CmpPthSz(pth,sz) SzCmp(pth + (strlen(pth) - strlen(sz)), sz)
// Read a script file before processing to see if we're going to try
// to do anything wrong.
//
// This script file reading code is based on that in FDoScript().
//
// LATER:
// The fact that we read and parse the script operations with
// InitNextSo and FNextSo makes this validation somewhat inefficient.
private F
FValidateScript(
FX fx)
{
extern AD adGlobal;
char szPhys[cchPthMax];
int fdScript;
char *pchScr, *sz1, *sz2;
char rgchScr[cchScrMax+1];
char *szScr;
SO so;
unsigned long cbFile;
// caller must assure that this fd is valid
fdScript = mpfxscr[fx].fd;
szScr = mpfxscr[fx].pth;
SzPhysPath(szPhys, szScr);
// close the file to flush the buffers
if (_close(fdScript) != 0) {
mpfxscr[fx].fd = fdNil;
Error(szErrorClose, mpfxsz[fx]);
return (fFalse);
}
fdScript = mpfxscr[fx].fd = _open(szPhys, omReadWrite);
if (-1 == fdScript) {
Error("error opening script %s\n", szScr);
return (fFalse);
}
// empty script files are never valid, there must be a comment
cbFile = _lseek(fdScript, 0, SEEK_END);
if (0L == cbFile || -1L == cbFile) {
Error(szErrorEmpty, mpfxsz[fx]);
return (fFalse);
}
// move file pointer back to beginning of file for script processing
if (_lseek(fdScript, 0, SEEK_SET) != 0L) {
Error("failed seeking to beginning of script file %s\n", szScr);
return fFalse;
}
InitNextSo(rgchScr, &pchScr);
do {
if (!FNextSo(&so, &sz1, &sz2, fdScript, rgchScr, &pchScr)) {
Error("error in script file %s\n", szScr);
return fFalse;
}
switch (so) {
default: AssertF(fFalse);
case soInstall:
//PrErr("\nsoInstall:\n\t%s\n\t%s\n\n", sz1, sz2);
if (CmpPthSz(sz2, "status.slm") == 0 &&
CbFile(sz1) != CbStatusCalculate(sz1))
{
AbortScript();
FatalError("Error installing new status file. "
"If retrying the command does\n"
"not work, please email the TRIO or NUTS alias for support.\n");
}
break;
case soInstall1Ed:
//PrErr("\nsoInstall1Ed:\n\t%s\n\t%s\n\n", sz1, sz2);
break;
case soAppend:
//PrErr("\nsoAppend:\n\t%s\n\t%s\n\n", sz1, sz2);
// if an append to a diff file check temp file for correct checksum
if (_strnicmp(sz1, adGlobal.pthSRoot, strlen(adGlobal.pthSRoot)) == 0)
if (_strnicmp("/diff/", &sz1[strlen(adGlobal.pthSRoot)], 6) == 0)
CheckDiffEntry(sz1);
#if 0
// LATER: Check for min diff size
if (strstr(sz2, "diff") != 0 &&
(cbFile = CbFile(sz1)) < CbDiffMin)
{
FatalError("Error installing diff file. Check server disk space and retry command.\n");
}
#endif
break;
case soRename:
case soRenReal:
{
int fd = -1;
int ctry = 0;
SzPhysPath(sz1, sz1);
SzPhysPath(sz2, sz2);
while ((fd = _sopen(sz2, O_RDONLY, SH_DENYRW)) == -1) {
if (errno != EACCES)
break;
if (ctry++ == ctryMax) {
AbortScript();
FatalError("Sharing violation accessing %s; file may be in use.\n", sz2);
}
if ((ctry % ctryAuto) == 0
&& !FQueryApp("Sharing violation accessing %s; file may be in use", "retry", sz2))
{
AbortScript();
FatalError("Sharing violation accessing %s; file may be in use.\n", sz2);
}
SleepTicks(1);
}
if (fd != -1)
_close(fd);
}
case soLink:
case soRemark:
case soClear:
case soDelete:
case soMakeRW:
case soMakeRO:
case soCreate:
case soExit:
case soEof:
break;
}
} while (so != soEof && so != soExit);
return fTrue;
}
// Close script(s) and run them.
void
RunScript()
{
if (FOpenedFx(fxGlobal))
if (!FValidateScript(fxGlobal)) {
Error("validation of global script failed -- script aborted\n");
AbortScript();
return;
}
if (FOpenedFx(fxLocal))
if (!FValidateScript(fxLocal)) {
AbortScript();
return;
}
ProcessScript("installing files", doscRun);
}
// Undo each scripted action in either script.
void
AbortScript()
{
ProcessScript("aborting", doscAbort);
}
// Close script(s) and process them according to dosc
private void
ProcessScript(
char *sz,
DOSC dosc)
{
F fExistsLocal = FOpenedFx(fxLocal);
F fExistsGlobal = FOpenedFx(fxGlobal);
DeferSignals(sz);
// close both scripts before processing either so that we don't
// get situation where one script is complete and can therefore
// be run, and the other is incomplete and must be aborted.
if (fExistsLocal) {
CloseFx(fxLocal, dosc);
if (fExistsGlobal) {
CloseFx(fxGlobal, dosc);
DoFx(fxGlobal, dosc);
}
DoFx(fxLocal, dosc);
} else {
// Assume global script implies existence of local
AssertF(!fExistsGlobal);
}
RestoreSignals();
}
// perform the given type of processing on the script file which corresponds
// to the given fx. The fx must have been closed.
private void
DoFx(
FX fx,
DOSC dosc)
{
AssertNoMf();
AssertF(FValidFx(fx));
AssertF(!FOpenedFx(fx));
AssertF(mpfxscr[fx].pth[0]);
FDoScript(mpfxscr[fx].pth, dosc, fFalse);
}
static F fPrScript = fFalse;
// Return fTrue if all applicable scripts are successfully run/aborted.
F
FDoAllScripts(
AD *pad,
LCK lck,
F fPrompt,
F fPrintScripts)
{
NE *pneScripts = PneScripts(pad, lck, pad->iedCur);
NE *pne;
F fOk = fTrue;
DOSC dosc;
fPrScript = fVerbose && fPrintScripts;
ForEachNe(pne, pneScripts) {
DeferSignals("running script");
dosc = FHasExit(SzOfNe(pne)) ? doscRerun : doscAbort;
fOk &= FDoScript(SzOfNe(pne), dosc, fPrompt);
RestoreSignals();
}
FreeNe(pneScripts);
fPrScript = fFalse;
return fOk;
}
// return fTrue is file exists and last line contains "exit"
private F
FHasExit(
PTH *pth) // converted in place
{
int fd;
F fHasExit = fFalse;
char rgb[cchExitLn+2]; // +2 for \r\n
char szPth[cchPthMax];
if ((fd = _open(SzPhysPath(szPth, pth), omReadOnly)) >= 0) {
if (_lseek(fd, -(long)sizeof(rgb), 2) != -1L &&
_read(fd, rgb, sizeof(rgb)) == sizeof(rgb))
{
register char *pch;
pch = &rgb[sizeof(rgb)]; // point to mac
if (*(pch-1) == '\n') {
// must have \n on very end to qualify
pch--;
if (*(pch-1) == '\r')
// may have intervening \r
pch--;
fHasExit = strncmp(pch-cchExitLn, szExitLn, cchExitLn) == 0;
}
}
_close(fd);
}
return fHasExit;
}
// { Run, rerun, abort } the script operations in the named script.
private F
FDoScript(
PTH *pthScr,
DOSC dosc,
F fPrompt) // ask the user before processing?
{
#define clineEachUpdate 5
int ln;
int fdScript;
char szScr[cchPthMax];
int cch, iBuf;
long wPos, lPosSave;
int clineScript = 0; // total lines in the script file
char szBuf[100]; // read 100 bytes at a time to count lines
SO so;
F fSomeError = fFalse;
char *pchScr;
char rgchScr[cchScrMax+1];
static char *rgszVerb[] = { "process", "rerunn", "abort" };
static char *rgszPrompt[] = { "process it", "rerun it", "abort it" };
static char *rgszFix[] =
{
"run sadmin runscript",
"finish by hand and remove",
"removing script anyway; you may have to remove some temp files"
};
AssertF(dosc == doscRun || dosc == doscRerun || dosc == doscAbort);
if ((fdScript = _open(SzPhysPath(szScr, pthScr), omReadOnly)) < 0) {
Error("can't open script file %s\n", szScr);
return fFalse;
}
// find out how many lines are in the script file
lPosSave = _tell(fdScript);
while ((cch = _read(fdScript, szBuf, sizeof(szBuf))) != 0) {
if (_doserrno == enInterruptSysCall)
AssertF(_lseek(fdScript, lPosSave, 0) == lPosSave);
else if (cch < 0) {
Error("error reading script file %s\n", szScr);
fSomeError = fTrue;
break;
}
// find the end of lines
for (iBuf = 0; iBuf < cch; iBuf++) {
if (szBuf[iBuf] == '\n')
clineScript++;
}
lPosSave = _tell(fdScript);
}
// move file pointer back to beginning of file for script processing
if ((wPos = _lseek(fdScript, 0L, 0)) != 0L) {
Error("failed seeking to beginning of script file %s\n", szScr);
fSomeError = fTrue;
}
// read each line and process
InitNextSo(rgchScr, &pchScr);
for (ln = 1; ; ln++) {
char *sz1, *sz2;
if (!FNextSo(&so, &sz1, &sz2, fdScript, rgchScr, &pchScr)) {
Error("error in script file %s, line %d\n", szScr, ln);
fSomeError = fTrue;
break;
}
if (so == soEof || so == soExit)
break;
// Query the user for approval before processing the script.
if (so == soRemark && fPrompt) {
// assume that this is the first line of the script
AssertF(ln == 1);
if (!FQueryApp("script file %s from operation\n\"%s\" exists",
rgszPrompt[dosc],
szScr,
sz1))
{
return fFalse;
}
else if (FForce() && !fVerbose) {
// always at least warn the user
Warn("%sing script file %s from operation\n\"%s\"\n",
rgszVerb[dosc],
szScr,
sz1);
}
} else {
// first line of script must be remark
AssertF(!fPrompt || ln != 1);
}
switch (dosc) {
case doscRerun:
if (!FUnDoneSo(so, sz1, sz2, szScr))
break;
// fall through
case doscRun:
fSomeError |= !FRunSo(so, sz1, sz2, szScr);
break;
case doscAbort:
AbortSo(so, sz1);
break;
}
// write out the percentage of the script file that has been
// processed so far. Update this percentage every 5 lines
// processed.
if (ln % clineEachUpdate == 0 && ln != 0)
PrintPercentDone(ln, clineScript, szScr, dosc);
}
// print off the final 100% message that script is finished
if (clineScript >= 5) {
// if there is no EXIT as last script line (ie. broke
// out of command), then ln = clineScript+1 as ln gets
// incremented one more past 'last line' of script.
if (so == soEof)
clineScript++;
PrintPercentDone(ln, clineScript, szScr, dosc);
PrErr("\n");
}
if (fSomeError)
Error("error(s) in %sing %s; %s\n", rgszVerb[dosc], szScr,
rgszFix[dosc]);
if (_close(fdScript) != 0)
FatalError("failed closing script file %s\n", szScr);
if ((!fSomeError || dosc == doscAbort) && _unlink(szScr) != 0) {
Error("cannot remove script file %s (%s)\n",
szScr, SzForEn(errno));
fSomeError = fTrue;
}
return !fSomeError;
}
// calculate what percentage of the script file has been run so far, given the
// number of lines in the script file and the number of lines that we have
// executed thus far.
private void
PrintPercentDone(
int ln,
int clineScript,
char *szScr,
DOSC dosc)
{
unsigned long wPercentDone;
char *pch;
char *szScrFile;
wPercentDone = (unsigned long) ln*100L/(unsigned long)clineScript;
if (szOp != 0)
{
if (!CarriageReturn())
return;
PrErr("%s: ", szOp);
}
pch = rindex(szScr, '/');
szScrFile = pch + 1;
CheckForBreak();
if ((_strcmpi(szScrFile, "local.scr")) == 0)
if (dosc == doscAbort)
PrErr("restoring local files: %lu%%", wPercentDone);
else
PrErr("installing local files: %lu%%", wPercentDone);
else if (dosc == doscAbort)
PrErr("restoring server files: %lu%%", wPercentDone);
else
PrErr("installing server files: %lu%%", wPercentDone);
}
// perform one step of the script file; return fFalse if error; message
// printed if error.
private F
FRunSo(
SO so,
char *sz1,
char *sz2,
char *szScr)
{
if (sz1 != 0 && so != soRemark)
SzPhysPath(sz1, (PTH *)sz1);
if (sz2 != 0)
SzPhysPath(sz2, (PTH *)sz2);
switch(so) {
default: AssertF(fFalse);
case soEof:
AssertF(sz1 == 0 && sz2 == 0);
Error("premature end of script file %s\n", szScr);
return fFalse;
case soAppend:
if (!FAppendSz(sz1, sz2)) {
Error("cannot append %s to %s\n", sz1, sz2);
return fFalse;
}
// fall through
case soClear:
case soDelete:
if (!FDeleteSz(sz1))
return fFalse;
break;
case soInstall1Ed:
AssertF(sz1 != 0);
AssertF(sz2 != 0);
if (!FInstall1Ed(sz2, sz1))
return fFalse;
_unlink(sz1); // ignore error
break;
case soInstall:
// backup $2
AssertF(sz1 != 0);
AssertF(sz2 != 0);
if (!FRenSz(sz2, (char *)0))
return fFalse;
// fall through to rename
case soRenReal:
case soRename:
AssertF(sz1 != 0);
AssertF(sz2 != 0);
if (!FRenSz(sz1, sz2))
return fFalse;
break;
case soCreate:
AssertF(sz1 != 0);
break;
case soLink:
AssertF(sz1 != 0);
AssertF(sz2 != 0);
Error("cannot execute link command in script file %s\n", szScr);
return fFalse;
break;
case soMakeRW:
case soMakeRO:
AssertF(sz1 != 0);
// don't fail the script if it fails
(void)FMakeRO(sz1, so == soMakeRO);
break;
case soExit:
AssertF(sz1 == 0);
break;
case soRemark:
AssertF(sz1 != 0);
AssertF(sz2 == 0);
break;
}
return fTrue;
}
// return fTrue if the so has not yet been completed
private F
FUnDoneSo(
SO so,
char *sz1,
char *sz2,
char *szScr)
{
struct _stat st1;
struct _stat st2;
switch (so) {
default: AssertF(fFalse);
case soEof:
AssertF(sz1 == 0);
AssertF(sz2 == 0);
Error("premature end of script file %s\n", szScr);
break;
case soClear:
case soDelete:
case soRenReal:
case soRename:
case soAppend:
case soInstall:
case soInstall1Ed:
AssertF(sz1 != 0);
if (FPthExists((PTH *)sz1, fFalse))
return fTrue;
break;
case soLink:
AssertF(sz1 != 0);
AssertF(sz2 != 0);
Unreferenced(st2);
return fTrue; // FRunSo will complain.
break;
case soMakeRW:
case soMakeRO:
#define MODE S_IWRITE
AssertF(sz1 != 0);
if (FStatPth((PTH *)sz1, &st1) &&
((st1.st_mode&MODE) != 0) != (so == soMakeRW))
return fTrue;
break;
case soCreate:
AssertF(sz1 != 0);
break;
case soExit:
AssertF(sz1 == 0);
break;
case soRemark:
AssertF(sz1 != 0);
AssertF(sz2 == 0);
break;
}
return fFalse;
}
// abort one line of the script file
private void
AbortSo(
SO so,
char *sz1)
{
switch(so) {
default: AssertF(fFalse);
case soAppend:
case soDelete:
case soInstall:
case soInstall1Ed:
case soRename:
case soCreate:
AssertF(sz1 != 0);
SzPhysPath(sz1, (PTH *)sz1);
PrintSo(soDelete, sz1, (char *)0);
if (SLM_Unlink(sz1) != 0 && so != soDelete)
Warn("cannot remove %s\n", sz1);
break;
case soClear:
case soLink:
case soMakeRW:
case soMakeRO:
case soEof:
case soExit:
case soRemark:
case soRenReal:
break;
}
}
// print message indicating what we are doing on stderr; only some so are
// supported.
private void
PrintSo(
SO so,
char *sz1,
char *sz2)
{
if (!fPrScript)
return;
AssertF(sz1 != 0);
switch (so) {
default: AssertF(fFalse);
case soAppend:
AssertF(sz2 != 0);
PrErr("Append %s to %s\n", sz1, sz2);
break;
case soDelete:
PrErr("Delete %s\n", sz1);
break;
case soLink:
AssertF(sz2 != 0);
PrErr("Link %s to %s\n", sz2, sz1);
break;
case soMakeRW:
case soMakeRO:
PrErr("Change %s to be %s\n", sz1, so==soMakeRO ? "readonly" : "writeable");
break;
case soRenReal:
case soRename:
AssertF(sz2 != 0);
PrErr("Rename %s to %s\n", sz1, sz2);
break;
}
}
// rename sz1 to sz2; return fFalse on error; message already printed. If
// sz2 == 0, we make a name out of sz1.
private F
FRenSz(
char *sz1,
char *sz2)
{
char szT[cchPthMax];
int wRet;
if (sz2 == 0) {
char *pch;
sz2 = strcpy(szT, sz1);
if ((pch = rindex(sz2, '.')) != 0)
// was xxxxxxxx.yyy; make it xxxxxxxx.bak
strcpy(pch+1, "bak");
else
// was zzzzzzz; make it zzzzzzz-
strcat(sz2, "-");
}
AssertF(sz1 != 0);
PrintSo(soRename, sz1, sz2);
BLOCK
// we cannot delete locked files on DOS
{
int ctry = 0;
while ((SLM_Unlink(sz2) || SLM_Rename(sz1, sz2))) {
struct _stat st;
if (ctry++ == ctryMax) {
if (!FCanPrompt())
Error("retry count over maximum (cannot rename %s to %s)\n",
sz1, sz2);
return (fFalse);
}
if ((ctry % ctryAuto == 0)
&& !FQueryApp("cannot rename %s to %s", "retry", sz1, sz2))
return (fFalse);
// if sz1 was successfully renamed to sz2, even when
// the system call gets interrupted, then we assume
// success and continue
if ((wRet = _stat(sz1, &st) != 0) && (wRet = _stat(sz2, &st) == 0))
break;
}
}
return (fTrue);
}
// make file readonly (or writeable) depending upon fRO; return fFalse on
// erorr; message already printed.
private F
FMakeRO(
char *sz,
int fRO)
{
int status;
AssertF(sz != 0);
PrintSo(fRO ? soMakeRO : soMakeRW, sz, (char *)0);
if ((status = setro(sz, fRO)) != 0 &&
(fRO || status != 2) // not found
)
{
Error("cannot make %s %s\n",sz, fRO ? "readonly" : "writeable");
return fFalse;
}
return fTrue;
}
// Called from RunScript, append the file szTemp to the end of szReal. First
// we scan backwards over spaces in the real file (added by CheckAppendMf),
// then copy the temp file over the spaces.
private F
FAppendSz(
char *szTemp,
char *szReal)
{
int fdTemp;
int fdReal;
POS pos;
int ib;
int cb;
char rgb[1024];
AssertF(szTemp);
AssertF(szReal);
PrintSo(soAppend, szTemp, szReal);
if ((fdTemp = _open(szTemp, omReadOnly )) < 0)
return fFalse;
if ((fdReal = _open(szReal, omReadWrite)) < 0)
goto close1;
// Read the file backwards, a block at a time, until we find a
// non-space, or run out of file. This code bears an uncanny
// resemblance to LcbSpaces.
if ((pos = _lseek(fdReal, (POS)0, 2)) < 0)
goto close2;
while (pos > 0) {
pos -= cb = WMinLL(sizeof rgb, pos);
if (_lseek(fdReal, pos, 0) < 0 || ReadLpbCb(fdReal, rgb, cb) != cb)
goto close2;
for (ib = cb - 1; ib >= 0 && rgb[ib] == ' '; ib--)
;
if (ib >= 0) {
pos += ib + 1;
break;
}
}
if (_lseek(fdReal, pos, 0) < 0)
goto close2;
//LATER: this code doesn't look right. The write() at least should
// be protected as other writes are, with a partial write loop,
// calling WRetryError and ClearPreviousError. However, this does
// look like it would report any problems, and we aren't seeing
// those problems, so we haven't changed it yet.
while ((cb = _read(fdTemp, rgb, sizeof rgb)) > 0 &&
WriteLpbCb(fdReal, rgb, cb) == cb)
;
return (cb == 0) & // Note: not &&!
(_close(fdReal) == 0) &
(_close(fdTemp) == 0);
close2: _close(fdReal);
close1: _close(fdTemp);
return fFalse;
}
// remove file; return fFalse on error; message already printed
private F
FDeleteSz(
char *sz)
{
PrintSo(soDelete, sz, (char *)0);
AssertF(sz != 0);
if (SLM_Unlink(sz) != 0 && _doserrno != ERROR_FILE_NOT_FOUND) {
Error("cannot remove %s\n", sz);
return fFalse;
}
return fTrue;
}
// prepare for the first call to FNextSo()
private void
InitNextSo(
char rgchScr[], // size == cchScrMax+1
char **ppchScr)
{
rgchScr[cchScrMax] = '\0';
rgchScr[0] = ' ';
rgchScr[1] = '\0';
*ppchScr = rgchScr+1;
}
// read/scan the next line for the Script Operation and the one or two
// arguments. *pso, *psz1, *psz2 and *ppchScr are updated.
//
// return fTrue if successfull, fFalse on error (no message printed).
//
// EOF returns fTrue and special so.
private F
FNextSo(
SO *pso,
char **psz1,
char **psz2,
int fd,
char rgchScr[], // size == cchScrMax+1 and always zero terminated
char **ppchScr)
{
register char *pchScr = *ppchScr, *pch;
AssertF(rgchScr[cchScrMax] == '\0');
AssertF(pchScr >= rgchScr);
if (index(pchScr, '\n') == 0) {
// no end of line; shift remaining amount over and read block
int cb;
if (pchScr == rgchScr)
// no \n and we are still at the beginning
return fFalse;
strcpy(rgchScr, pchScr);
pchScr = index(rgchScr, '\0');
if ((cb = _read(fd, pchScr, cchScrMax-(pchScr-rgchScr))) < 0)
return fFalse;
if (cb == 0) {
*pso = soEof;
*psz1 = 0;
*psz2 = 0;
return fTrue;
}
*(pchScr+cb) = '\0';
pchScr = rgchScr;
}
// find end, save and terminate
if ((pch = index(pchScr, '\n')) == 0)
return fFalse;
*ppchScr = pch+1;
*pch = '\0';
if (*(pch-1) == '\r')
*(pch-1) = '\0';
switch (*pchScr) {
default: return fFalse;
case 'a': *pso = soAppend; break;
case 'c': *pso = (pchScr[1] == 'l') ? soClear : soCreate; break;
case 'd': *pso = soDelete; break;
case 'e': *pso = soExit; break;
case 'i': *pso = *(pchScr+7) == '1' ? soInstall1Ed : soInstall; break;
case 'l': *pso = soLink; break;
case 'm': *pso = *(pchScr+5) == 'w' ? soMakeRW : soMakeRO; break;
case 'r': *pso = *(pchScr+2) == 'm' ? soRemark :
(*(pchScr+3) == 'a' ? soRename : soRenReal); break;
}
switch (*pso) {
default: AssertF(fFalse);
case soAppend:
case soInstall:
case soInstall1Ed:
case soRename:
case soRenReal:
case soLink:
if ((pch = index(pchScr, ' ')) == 0)
return fFalse;
*psz1 = pch+1; // first argument
if ((pch = index(pch+1, ' ')) == 0)
return fFalse;
*pch = '\0'; // terminate first argument
*psz2 = pch+1; // second argument
if ((pch = index(pch+1, ' ')) != 0)
return fFalse;
break;
case soRemark:
if ((pch = index(pchScr, ' ')) == 0)
return fFalse;
*psz1 = pch+1; // first argument
*psz2 = 0; // no second argument
break;
case soClear:
case soDelete:
case soMakeRW:
case soMakeRO:
case soCreate:
if ((pch = index(pchScr, ' ')) == 0)
return fFalse;
*psz1 = pch+1; // first argument
if ((pch = index(pch+1, ' ')) != 0)
return fFalse;
*psz2 = 0; // no second argument
break;
case soExit:
*psz1 = 0;
*psz2 = 0;
break;
}
// make sure that the path names are not too long
if (*psz1 != 0 && strlen(*psz1) > cchPthMax)
return fFalse;
if (*psz2 != 0 && strlen(*psz2) > cchPthMax)
return fFalse;
return fTrue;
}
F
CarriageReturn(
void)
{
HANDLE hOutput = GetStdHandle(STD_ERROR_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO ScrInfo;
COORD Pos;
DWORD dwNumWritten;
if (!GetConsoleScreenBufferInfo(hOutput, &ScrInfo))
return fFalse;
// New position is same line, first column
Pos.X = ScrInfo.dwCursorPosition.X = 0;
Pos.Y = ScrInfo.dwCursorPosition.Y;
if (!SetConsoleCursorPosition(hOutput, Pos))
return fFalse;
// Blank the line
FillConsoleOutputCharacter(hOutput, ' ', ScrInfo.dwSize.X, Pos,
&dwNumWritten );
return fTrue;
}