mirror of https://github.com/lianthony/NT4.0
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.
1309 lines
30 KiB
1309 lines
30 KiB
// args - parse the superfluity of command line arguments
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
EnableAssert
|
|
|
|
#pragma warning(4:4756)
|
|
|
|
// Parsing function type.
|
|
typedef void (*PFNP)(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
|
|
// Parsing functions indirectly called from ParseArgs
|
|
private void ParseComment(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseDir(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseDpv(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseFiles(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseFv(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseIRange(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseKind(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseNone(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseOpt(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParsePat(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParsePn(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseProj(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseRepl(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseSRoot(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseSz(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseSz1(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseSz2(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseTD(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseTRange(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseUName(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseHelp(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseLogFile(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
private void ParseHWnd(AD *pad, AT at, char ***ppsz, char **pszMac);
|
|
|
|
private PFNP mpatpfnp[]= { // Parsing function for each argument type.
|
|
ParseNone, // atNone
|
|
ParseNone, // atFlag
|
|
ParseSRoot, // atSlmRoot
|
|
ParseProj, // atProject
|
|
ParseOpt, // atOptProject
|
|
ParseProj, // atProjOptSubDir
|
|
ParseOpt, // atOptProjOptSubDir
|
|
ParseProj, // atNewProj
|
|
ParseDir, // atDir
|
|
ParseFiles, // atFiles
|
|
ParseFiles, // atFiletimes
|
|
ParseComment, // atComment
|
|
ParseTD, // atOneTime
|
|
ParseTRange, // atTimeRange
|
|
ParseTD, // atMinTime (internal)
|
|
ParseTD, // atMacTime (internal)
|
|
ParseIRange, // atCountRange
|
|
ParseKind, // atKind
|
|
ParsePat, // atOptPat
|
|
ParseUName, // atUserName
|
|
0, // atDelRepl not implemented on DOS
|
|
ParsePn, // atPn
|
|
ParseDpv, // atPvDirs
|
|
ParseFv, // atFvFiles
|
|
ParseSz, // atSzDirs
|
|
ParseSz, // atSz
|
|
ParseOpt, // atOptSz
|
|
ParseHelp, // atHelp
|
|
ParseLogFile, // atLogFile
|
|
ParseHWnd, // atWindows
|
|
ParseSz1, // atSz1
|
|
ParseOpt, // atOptSz1
|
|
ParseSz2, // atSz2
|
|
ParseOpt // atOptSz2
|
|
};
|
|
|
|
// Parsing functions' support routines
|
|
private FT *PftLookupCh(FT [], char);
|
|
private F FParseProj(AD *, char ***, char **, NM *);
|
|
private void ParseSubDir(AD *pad, char ***ppsz, char **pszMac);
|
|
private F FParsTd(char *, TD *, F, F);
|
|
private F FTryParseTd(char *, TD *, F, F);
|
|
private F FParsDpv(AD *, char ***, char **);
|
|
private F FPrsPN(char *, char *);
|
|
private F FParseTime(char *, int *, int *, int *, F);
|
|
private F FParseDate(TIME *, char *, F);
|
|
private TIME TimeFromDate(int, int, int);
|
|
private F FWasDst(TIME *);
|
|
private SEC SecIntoDay(TIME);
|
|
private void ParseINum(short *, char ***, char **, F);
|
|
private void ParseOptPV(AD *, char ***, char **);
|
|
|
|
char getswitch(void);
|
|
#define FSwitchar(ch) ((ch) == '-' || (ch) == getswitch())
|
|
|
|
// parse arguments passed; ignores first one (as it should be program name);
|
|
// sets the appropriate fields of the ad; does not return if error
|
|
|
|
void
|
|
ParseArgs(
|
|
AD *pad,
|
|
char *rgsz[],
|
|
int iszMac
|
|
)
|
|
{
|
|
register FT *pft;
|
|
char *sz, **psz, **pszMac;
|
|
AT at;
|
|
|
|
AssertF(iszMac > 0);
|
|
psz = rgsz + 1;
|
|
pszMac = psz + iszMac - 1;
|
|
|
|
// for all hyphen-preceeded arguments
|
|
while (psz < pszMac && FSwitchar(**psz)) {
|
|
pft = 0;
|
|
sz = *psz + 1;
|
|
for (;;) {
|
|
// look for character in Flag Transition table
|
|
if ((pft = PftLookupCh(pad->pecmd->rgft, *sz)) == 0) {
|
|
Error("unknown switch: %c\n", *sz);
|
|
Usage(pad);
|
|
}
|
|
|
|
if (flagErrToOut == pft->flag)
|
|
ChngErrToOut();
|
|
else
|
|
pad->flags |= pft->flag;
|
|
|
|
// if not a simple flag and not an optional pattern
|
|
if (pft->at != atFlag &&
|
|
(pft->at != atWindows || (*(sz + 1) == ':')) &&
|
|
(pft->at != atOptPat || FWildSz(sz + 1))) {
|
|
// Get flag's argument. If more characters remaining, use
|
|
// them, otherwise use next string argument.
|
|
|
|
if (pft->at == atCountRange)
|
|
*psz = sz;
|
|
else if (*(sz + 1))
|
|
*psz = sz + 1;
|
|
else
|
|
++psz;
|
|
break;
|
|
} else if (!*++sz) {
|
|
// no more flag arguments
|
|
++psz;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!pft) {
|
|
Error("'-' specified without flags\n");
|
|
Usage(pad);
|
|
}
|
|
|
|
at = pft->at;
|
|
AssertF(FValidAt(at));
|
|
AssertF(mpatpfnp[at] != 0);
|
|
(*mpatpfnp[at])(pad, at, &psz, pszMac);
|
|
}
|
|
|
|
AssertF(psz <= pszMac);
|
|
|
|
// Parse remaining arguments.
|
|
at = pad->pecmd->atExtra;
|
|
AssertF(FValidAt(at));
|
|
AssertF(mpatpfnp[at] != 0);
|
|
(*mpatpfnp[at])(pad, at, &psz, pszMac);
|
|
|
|
// Should have consumed the remainder of the arguments.
|
|
if (psz != pszMac)
|
|
Usage(pad);
|
|
}
|
|
|
|
|
|
// return the pft for the character, 0 if none
|
|
private FT *
|
|
PftLookupCh(
|
|
FT rgft[],
|
|
char ch
|
|
)
|
|
{
|
|
register FT *pft;
|
|
|
|
// this depends upon all ft entries being in lower case
|
|
ch = (char)tolower(ch);
|
|
|
|
for (pft = rgft; pft->chFlag != 0; pft++) {
|
|
if (pft->chFlag == '#') {
|
|
// # represents a digit
|
|
if (ch >= '0' && ch <= '9')
|
|
return pft;
|
|
} else
|
|
if (pft->chFlag == ch)
|
|
return pft;
|
|
}
|
|
return (FT *)0;
|
|
}
|
|
|
|
|
|
// Parse optional parameters. If more parameters remain, call the parsing
|
|
// function for the at mapped by mpatatNonOpt.
|
|
|
|
private void
|
|
ParseOpt(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
// Mapping from "optional" at to its non-optional counterpart.
|
|
static AT mpatatNonOpt[] = {
|
|
atNone, // atNone
|
|
atNone, // atFlag
|
|
atNone, // atSlmRoot
|
|
atNone, // atProject
|
|
atProject, // atOptProject
|
|
atNone, // atProjOptSubDir
|
|
atProjOptSubDir,// atOptProjOptSubDir
|
|
atNone, // atNewProj
|
|
atNone, // atDir
|
|
atNone, // atFiles
|
|
atNone, // atFiletimes
|
|
atNone, // atComment
|
|
atNone, // atOneTime
|
|
atNone, // atTimeRange
|
|
atNone, // atMinTime
|
|
atNone, // atMacTime
|
|
atNone, // atCountRange
|
|
atNone, // atKind
|
|
atNone, // atOptPat
|
|
atNone, // atUserName
|
|
atNone, // atDelRepl
|
|
atNone, // atPn
|
|
atNone, // atPvDirs
|
|
atNone, // atFvFiles
|
|
atNone, // atSzDirs
|
|
atNone, // atSz
|
|
atSz, // atOptSz
|
|
atNone, // atHelp
|
|
atNone, // atLogFile
|
|
atNone, // atWindows
|
|
atNone, // atSz2
|
|
atSz1, // atOptSz1
|
|
atNone, // atSz2
|
|
atSz2 // atOptSz2
|
|
};
|
|
|
|
AssertF(at == atOptProject || at == atOptProjOptSubDir || at ==atOptSz || at ==atOptSz1 || at ==atOptSz2);
|
|
|
|
if (*ppsz < pszMac) {
|
|
at = mpatatNonOpt[at];
|
|
AssertF(FValidAt(at));
|
|
AssertF(at != atNone);
|
|
AssertF(mpatpfnp[at] != 0);
|
|
(*mpatpfnp[at])(pad, at, ppsz, pszMac);
|
|
}
|
|
}
|
|
|
|
|
|
// Parse nothing.
|
|
private void
|
|
ParseNone(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
Unreferenced(pad);
|
|
Unreferenced(ppsz);
|
|
Unreferenced(pszMac);
|
|
|
|
AssertF(at == atNone || at == atFlag);
|
|
}
|
|
|
|
|
|
// Parse new project name, which may not include a subproject.
|
|
private void
|
|
ParseProj(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
NM *nmProj;
|
|
|
|
AssertF(at == atProject || at == atProjOptSubDir || at == atNewProj);
|
|
|
|
nmProj = (at == atNewProj) ? pad->nmNewProj : pad->nmProj;
|
|
|
|
if (FParseProj(pad, ppsz, pszMac, nmProj)) {
|
|
if (at == atProjOptSubDir) {
|
|
ParseSubDir(pad, ppsz, pszMac);
|
|
} else {
|
|
Error("do not specify project subdirectory\n");
|
|
Usage(pad);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Parses **ppsz into nmProj (2-8/14 chars). FatalErrors if error. Returns
|
|
// fTrue if a subdir remains to be parsed, leaving **ppsz pointing to it.
|
|
|
|
private F
|
|
FParseProj(
|
|
AD *pad,
|
|
char ***ppsz,
|
|
char **pszMac,
|
|
NM *nmProj
|
|
)
|
|
{
|
|
char **psz = *ppsz;
|
|
register char *sz;
|
|
char *pchSlash;
|
|
|
|
if (psz == pszMac)
|
|
Usage(pad);
|
|
|
|
// Extract project and optional subdirectory.
|
|
sz = *psz;
|
|
if ((pchSlash = index(sz, '/')) != 0 ||
|
|
(pchSlash = index(sz, '\\')) != 0) {
|
|
if (pchSlash == sz)
|
|
FatalError("must specify project name with project subdirectory\n");
|
|
*pchSlash = 0;
|
|
}
|
|
|
|
if (FWildSz(sz))
|
|
FatalError("project name may not include wild card characters {*,?}\n");
|
|
|
|
ValidateFileName(sz, TRUE);
|
|
NmCopySz(nmProj, sz, cchProjMax);
|
|
|
|
if (pchSlash != 0) {
|
|
*pchSlash = '/';
|
|
*psz = pchSlash;
|
|
return fTrue;
|
|
} else {
|
|
(*ppsz)++;
|
|
return fFalse;
|
|
}
|
|
}
|
|
|
|
|
|
// Parse the subdirectory at **ppsz.
|
|
private void
|
|
ParseSubDir(
|
|
AD *pad,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
char **psz = *ppsz;
|
|
char *sz;
|
|
|
|
AssertF(psz < pszMac);
|
|
sz = *psz;
|
|
ConvToSlash(sz);
|
|
AssertF(*sz == '/');
|
|
|
|
PthCopy(pad->pthSSubDir, sz);
|
|
|
|
// We don't want to call InferUSubDir for an sadmin udump command
|
|
// for two reasons:
|
|
// 1. InferUSubDir tries to load the current status file
|
|
// for the target directory. The reason we're undumping
|
|
// is to replace that status.slm -- it's probably invalid.
|
|
//
|
|
// 2. undump doesn't need the info it provides.
|
|
|
|
if (pad->pecmd->cmd != cmdUndump)
|
|
InferUSubDir(pad);
|
|
|
|
pad->flags |= flagProjectOverride;
|
|
|
|
(*ppsz)++;
|
|
}
|
|
|
|
|
|
// parse slm root
|
|
private void
|
|
ParseSRoot(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
char **psz = *ppsz;
|
|
|
|
AssertF(at == atSlmRoot);
|
|
|
|
if (psz == pszMac || !FPthLogicalSz(pad->pthSRoot, *psz))
|
|
Usage(pad);
|
|
|
|
pad->flags |= flagSlmRootOverride;
|
|
|
|
(*ppsz)++;
|
|
}
|
|
|
|
|
|
// save comment in AD
|
|
private void
|
|
ParseComment(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
AssertF(at == atComment);
|
|
|
|
if (*ppsz == pszMac)
|
|
Usage(pad);
|
|
|
|
pad->szComment = **ppsz;
|
|
(*ppsz)++;
|
|
}
|
|
|
|
|
|
extern HWND QueryHWnd; // defined in query.c
|
|
|
|
// save HWnd in global QueryHWnd
|
|
private void
|
|
ParseHWnd(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
char **psz = *ppsz;
|
|
|
|
AssertF(at == atWindows);
|
|
|
|
QueryHWnd = NULL;
|
|
if ((psz != pszMac) && (**psz == ':')) {
|
|
// validate window handle, or use NULL if invalid
|
|
if ((*PchGetW((*psz)+1, (int *)&QueryHWnd))==0) {
|
|
if ((QueryHWnd!=NULL) && (!IsWindow(QueryHWnd))) {
|
|
Warn("window handle %u is invalid; defaulting to NULL\n", QueryHWnd);
|
|
QueryHWnd = NULL;
|
|
}
|
|
} else {
|
|
Usage(pad);
|
|
}
|
|
|
|
(*ppsz)++;
|
|
}
|
|
}
|
|
|
|
// collect file[@time]s into pad->pneArgs; no absolute pathnames
|
|
private void
|
|
ParseFiles(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
F fTime; // expecting [@time]?
|
|
register char *sz;
|
|
char **psz;
|
|
char *pchAt;
|
|
NE *pne;
|
|
NE **ppneLast; // place to hook next element
|
|
TD td;
|
|
|
|
AssertF(at == atFiles || at == atFiletimes);
|
|
fTime = (at == atFiletimes);
|
|
|
|
pad->pneArgs = 0;
|
|
InitAppendNe(&ppneLast, &pad->pneArgs);
|
|
|
|
for (psz = *ppsz; psz < pszMac; psz++) {
|
|
sz = *psz;
|
|
|
|
ConvToSlash(sz);
|
|
|
|
if (sz[0] == '/')
|
|
FatalError("relative filenames only\n");
|
|
|
|
td.tdt = tdtNone;
|
|
pchAt = index(sz, '@');
|
|
if (pchAt) {
|
|
*pchAt = 0;
|
|
if (!fTime || !FParsTd(pchAt + 1, &td, fTrue, fTrue))
|
|
Usage(pad);
|
|
}
|
|
|
|
if (strlen(sz) == 0)
|
|
FatalError("no filename specified\n");
|
|
|
|
pne = PneNewNm((NM far *)sz, strlen(sz), faNormal);
|
|
pne->u.tdNe = td;
|
|
AppendNe(&ppneLast, pne);
|
|
}
|
|
|
|
// each application must check if a list of empty files is ok
|
|
*ppsz = pszMac;
|
|
}
|
|
|
|
// Cover function for FTryParseTd below, prints a verbose error message if
|
|
// pch doesn't point to a valid td.
|
|
|
|
private F
|
|
FParsTd(
|
|
char *pch,
|
|
TD *ptd,
|
|
F fFiletime,
|
|
F fFirst
|
|
)
|
|
{
|
|
if (!FTryParseTd(pch, ptd, fFiletime, fFirst)) {
|
|
Error("Invalid time '%s'. Time must be one of:\n", pch);
|
|
if (fFiletime)
|
|
PrErr("\tVfile-version\n");
|
|
PrErr("\tmajor.minor[.revision]\n"
|
|
"\t%sversion-name\n"
|
|
"\tmonth-day[-year][@hour:min[:sec]]\n"
|
|
"\t.[[+-]days][@hour:min[:sec]]\n\n",
|
|
fFiletime ? "" : "@");
|
|
return fFalse;
|
|
}
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
// Return TD parse of rgch, which can be one of these forms:
|
|
// v<n> (only if fFiletime)
|
|
// <n>.<n>[.<n>]
|
|
// <n>-<n>[-<n>][@<n>:<n>[:<n>]]
|
|
// .[[+-]<n>][@<n>:<n>[:<n>]]
|
|
// fFiletime ? <name> : @<name>
|
|
|
|
private F
|
|
FTryParseTd(
|
|
char *pch,
|
|
TD *ptd,
|
|
F fFiletime,
|
|
F fFirst
|
|
)
|
|
{
|
|
char *pch2;
|
|
int w;
|
|
|
|
ptd->tdt = tdtNone;
|
|
|
|
if (fFiletime && (*pch == 'v' || *pch == 'V') &&
|
|
(isdigit(*(pch+1)) || (*(pch+1) == '-' || *(pch+1) == '+')) &&
|
|
!*PchGetW(pch + 1, &w)) {
|
|
ptd->tdt = tdtFV;
|
|
ptd->u.fv = (FV)w;
|
|
return fTrue;
|
|
}
|
|
|
|
if (isdigit(*pch)) {
|
|
if (FParsPv(ptd, pch)) {
|
|
return fTrue;
|
|
} else
|
|
if (pch2 = PchGetW(pch, &w),
|
|
(*pch2 == '-' || *pch2 == '/') &&
|
|
FParseDate(&ptd->u.time, pch, fFirst)) {
|
|
|
|
ptd->tdt = tdtTime;
|
|
return fTrue;
|
|
}
|
|
}
|
|
|
|
if (*pch == '.' && FParseDate(&ptd->u.time, pch, fFirst)) {
|
|
ptd->tdt = tdtTime;
|
|
return fTrue;
|
|
}
|
|
|
|
if (*pch == '@' && FPrsPN(pch + 1, ptd->u.pv.szName) ||
|
|
fFiletime && FPrsPN(pch, ptd->u.pv.szName)) {
|
|
|
|
ptd->tdt = tdtPN;
|
|
return fTrue;
|
|
}
|
|
|
|
return fFalse;
|
|
}
|
|
|
|
// Try to parse project version, store pv in *ptd and return fTrue.
|
|
F
|
|
FParsPv(
|
|
TD *ptd,
|
|
char *pch
|
|
)
|
|
{
|
|
char *pch2, *pch3;
|
|
int w, w2, w3;
|
|
|
|
pch2 = PchGetW(pch, &w);
|
|
if (*pch2 == '.' && (pch3 = PchGetW(++pch2, &w2), pch3 > pch2) &&
|
|
(w3 = 0, !*pch3 || *pch3++ == '.' && !*PchGetW(pch3, &w3))) {
|
|
|
|
ptd->tdt = tdtPV;
|
|
ptd->u.pv.rmj = w;
|
|
ptd->u.pv.rmm = w2;
|
|
ptd->u.pv.rup = w3;
|
|
return fTrue;
|
|
}
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
// parse [delta-pv [dir(s)]]
|
|
private void
|
|
ParseDpv(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
AssertF(at == atPvDirs);
|
|
|
|
if (!FParsDpv(pad, ppsz, pszMac))
|
|
Usage(pad);
|
|
|
|
if (pad->dpv.rmj < 0 || pad->dpv.rmm < 0 || pad->dpv.rup < 0)
|
|
FatalError("each element of a version number must be"
|
|
" between 0 and %u\n", fvLim);
|
|
|
|
ParseFiles(pad, atFiles, ppsz, pszMac);
|
|
}
|
|
|
|
|
|
private F FParseComp( char *, F *, int *, char);
|
|
|
|
// Parse an OPTIONAL release pv into pad->dpv. A release pv may be a pv
|
|
// or may be of of the form [#].[#].[#], that is, three optional numbers,
|
|
// which may be prefaced by a '+' sign (to have a relative, rather than
|
|
// absolute, effect). Here are some examples:
|
|
// Release PV rmj' rmm' rup'
|
|
// ---------- --- --- ---
|
|
// 1.2 1 2 0
|
|
// 1.2.3 1 2 3
|
|
// 1.. 1 rmm rup
|
|
// .+1. rmj rmm+1 rup
|
|
// +1.0.0 rmj+1 0 0
|
|
// .+1.0 rmj rmm+1 0
|
|
// ..+1 rmj rmm rup+1
|
|
|
|
private F
|
|
FParsDpv(
|
|
AD *pad,
|
|
char ***ppsz,
|
|
char **pszMac)
|
|
{
|
|
char *sz = **ppsz;
|
|
TD td;
|
|
char *pchDot;
|
|
char *pchDot2;
|
|
|
|
// Set up a default value.
|
|
pad->dpv.fRelRmj = pad->dpv.fRelRmm = pad->dpv.fRelRup = fTrue;
|
|
pad->dpv.rmj = pad->dpv.rmm = pad->dpv.rup = 0;
|
|
|
|
if (*ppsz == pszMac)
|
|
return fTrue;
|
|
|
|
// May be an absolute pv (including #.#).
|
|
if (FParsPv(&td, sz)) {
|
|
pad->dpv.fRelRmj = pad->dpv.fRelRmm = pad->dpv.fRelRup = fFalse;
|
|
pad->dpv.rmj = td.u.pv.rmj;
|
|
pad->dpv.rmm = td.u.pv.rmm;
|
|
pad->dpv.rup = td.u.pv.rup;
|
|
|
|
++*ppsz;
|
|
return fTrue;
|
|
}
|
|
|
|
// Divide the delta-pv into three components.
|
|
pchDot = index(sz, '.');
|
|
pchDot2 = pchDot ? index(pchDot + 1, '.') : 0;
|
|
|
|
// Must now contain two '.'s.
|
|
if (pchDot == 0 || pchDot2 == 0)
|
|
return fFalse;
|
|
|
|
// Parse each component; each should consume all the characters
|
|
// up to the next dot (or null for the last component).
|
|
|
|
if (FParseComp(sz , &pad->dpv.fRelRmj, &pad->dpv.rmj, '.') &&
|
|
FParseComp(pchDot + 1, &pad->dpv.fRelRmm, &pad->dpv.rmm, '.') &&
|
|
FParseComp(pchDot2 + 1, &pad->dpv.fRelRup, &pad->dpv.rup, '\0')) {
|
|
|
|
++*ppsz;
|
|
return fTrue;
|
|
} else
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
// Return fTrue if we successfully parse +[number]<chTerm>
|
|
private F
|
|
FParseComp(
|
|
char *pchNum,
|
|
F *pfRel,
|
|
int *pw,
|
|
char chTerm
|
|
)
|
|
{
|
|
*pfRel = !isdigit(*pchNum);
|
|
if (*pchNum == '+')
|
|
++pchNum;
|
|
return *PchGetW(pchNum, pw) == chTerm;
|
|
}
|
|
|
|
|
|
private void
|
|
ParsePn(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
AssertF(at == atPn);
|
|
|
|
if (*ppsz < pszMac && FPrsPN(**ppsz, pad->tdMin.u.pv.szName)) {
|
|
++*ppsz;
|
|
pad->tdMin.tdt = tdtPN;
|
|
} else
|
|
Usage(pad);
|
|
}
|
|
|
|
|
|
// Check rgch for validity as a project version name and copy it to szName
|
|
private F
|
|
FPrsPN(
|
|
char *rgch,
|
|
char *szName
|
|
)
|
|
{
|
|
char *pch;
|
|
|
|
for (pch = rgch; *pch; pch++)
|
|
if (!(isprint(*pch) && *pch != ';')) {
|
|
Warn("name must contain printable, non-';' characters\n");
|
|
return fFalse;
|
|
}
|
|
|
|
strncpy(szName, rgch, cchPvNameMax);
|
|
szName[cchPvNameMax] = 0;
|
|
if (strlen(szName) < strlen(rgch))
|
|
Warn("name too long, truncated to \"%s\".\n", szName);
|
|
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
// parse file-version-number [file(s)]
|
|
private void
|
|
ParseFv(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
int wFv;
|
|
|
|
AssertF(at == atFvFiles);
|
|
|
|
if (*ppsz == pszMac || *PchGetW(**ppsz, &wFv) != 0)
|
|
Usage(pad);
|
|
pad->fv = wFv;
|
|
++*ppsz;
|
|
|
|
ParseFiles(pad, atFiles, ppsz, pszMac);
|
|
}
|
|
|
|
|
|
#define yearLeap 68 // 1968 - first leap year at or before yearMin
|
|
#define yearMin 70 // 1970
|
|
#define yearMax 100 // 2000 (allows up to 1999)
|
|
#define secDay 86400L // number of seconds in one day
|
|
|
|
#define secBX 28800 // number of seconds before 1970 starting point
|
|
|
|
static char szDate[] = "improper date %s; format is mm-dd-yy[@hh:mm[:ss]] or .[-#][@hh:mm[:ss]]\n";
|
|
|
|
// mapping from month to the count of days in the month
|
|
int mpmoncday[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
|
|
|
|
|
// parse time: [@hh:mm[:ss]]; defaults to 00:00:00
|
|
private F
|
|
FParseTime(
|
|
char *pch,
|
|
int *phour,
|
|
int *pminute,
|
|
int *psec,
|
|
F fFirst
|
|
)
|
|
{
|
|
if (*pch != '@') {
|
|
// no time
|
|
if (*pch != '\0')
|
|
return fFalse;
|
|
|
|
*phour = fFirst ? 0 : 24;
|
|
*pminute = 0;
|
|
*psec = 0;
|
|
} else {
|
|
pch++; // skip separator
|
|
|
|
// hour
|
|
pch = PchGetW(pch, phour);
|
|
if (*phour > 24 || *pch != ':')
|
|
return fFalse;
|
|
pch++;
|
|
|
|
// minute
|
|
pch = PchGetW(pch, pminute);
|
|
if (*pminute > 59)
|
|
return fFalse;
|
|
|
|
if (*pch != ':') {
|
|
// no seconds
|
|
if (*pch != '\0')
|
|
return fFalse;
|
|
*psec = 0;
|
|
} else {
|
|
pch++;
|
|
|
|
// second
|
|
pch = PchGetW(pch, psec);
|
|
if (*psec > 59 || *pch != '\0')
|
|
return fFalse;
|
|
}
|
|
}
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
// Parse the date/time in **ppsz; date is expected to be in the form:
|
|
// mm-dd[-yy][@hh:mm[:ss]]
|
|
// OR
|
|
// .[-#][@hh:mm[:ss]]
|
|
|
|
private F
|
|
FParseDate(
|
|
TIME *ptime,
|
|
char *pch,
|
|
F fFirst
|
|
)
|
|
{
|
|
int mon, dom, year, hour, minute, sec;
|
|
|
|
if (*pch == '.') {
|
|
// relative date
|
|
pch++; // skip .
|
|
|
|
if (fFirst && *pch == '\0') {
|
|
// just "."; use time of 00:00:00 Jan 1, 1970
|
|
*ptime = (TIME)0 + secBX;
|
|
return fTrue;
|
|
}
|
|
|
|
time(ptime); // now
|
|
|
|
if (*pch == '-' || *pch == '@')
|
|
// backup to midnight
|
|
*ptime -= SecIntoDay(*ptime);
|
|
|
|
// check for .-#
|
|
if (*pch == '-') {
|
|
int days;
|
|
|
|
pch++; // skip -
|
|
pch = PchGetW(pch, &days);
|
|
|
|
// backup specified # of days
|
|
*ptime -= days*secDay;
|
|
}
|
|
} else {
|
|
// Absolute date; first parse Month
|
|
pch = PchGetW(pch, &mon);
|
|
if (mon < 1 || mon > 12 || *pch != '/' && *pch != '-')
|
|
return fFalse;
|
|
pch++;
|
|
|
|
// Day Of Month
|
|
pch = PchGetW(pch, &dom);
|
|
if (dom < 1 || dom > mpmoncday[mon] + (mon == 2)) {
|
|
return fFalse; // February
|
|
}
|
|
|
|
if (*pch == '/' || *pch == '-') {
|
|
pch++;
|
|
|
|
// year
|
|
pch = PchGetW(pch, &year);
|
|
if (year < yearMin || year >= yearMax)
|
|
return fFalse;
|
|
} else
|
|
if (*pch == 0 || *pch == '@') { // default: current year
|
|
// Estimate the current year, check, and correct if
|
|
// our estimate was too high.
|
|
|
|
TIME timeT;
|
|
|
|
time(&timeT);
|
|
year = yearMin + (int)((timeT - secBX) / (365L*secDay));
|
|
if (timeT < TimeFromDate(year, 1, 1))
|
|
year--;
|
|
AssertF(timeT >= TimeFromDate(year, 1, 1));
|
|
AssertF(timeT < TimeFromDate(year + 1, 1, 1));
|
|
} else
|
|
return fFalse;
|
|
|
|
*ptime = TimeFromDate(year, mon, dom);
|
|
}
|
|
|
|
// have date (at midnight if abs or rel+time); parse [@hh:mm[:ss]]
|
|
if (!FParseTime(pch, &hour, &minute, &sec, fFirst))
|
|
return fFalse;
|
|
|
|
*ptime += 3600L*hour + 60*minute + sec;
|
|
|
|
if (FWasDst(ptime))
|
|
// if that time was during Daylight Savings Time, subtract one
|
|
// hour (even current time is ahead one hour)
|
|
|
|
*ptime -= 3600L;
|
|
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
// Convert the mon, dom, year into an absolute date. Absolute date is
|
|
// calculated with reference to base date, which is Jan 1, 1970.
|
|
|
|
private TIME
|
|
TimeFromDate(
|
|
int year,
|
|
int mon,
|
|
int dom
|
|
)
|
|
{
|
|
int fLeap;
|
|
int cleapsBefore;
|
|
register int monT;
|
|
|
|
fLeap = year%4 == 0; // true if this year is a leap year
|
|
if (fLeap)
|
|
mpmoncday[2] = 29;
|
|
|
|
for (monT = 1; monT < mon; monT++)
|
|
dom += mpmoncday[monT];
|
|
|
|
mpmoncday[2] = 28;
|
|
|
|
// number of leaps years before this year and after yearMin (not
|
|
// including yearMin).
|
|
|
|
cleapsBefore = (year - yearLeap) / 4 - (fLeap ? 1 : 0);
|
|
|
|
return secDay * ((year - yearMin)*365L + cleapsBefore + dom -1) + secBX;
|
|
}
|
|
|
|
|
|
// return fTrue if *ptime was during Daylight Savings Time
|
|
private F
|
|
FWasDst(
|
|
TIME *ptime
|
|
)
|
|
{
|
|
return localtime(ptime)->tm_isdst;
|
|
}
|
|
|
|
|
|
// for time, returns the number of seconds of day already passed
|
|
private SEC
|
|
SecIntoDay(
|
|
TIME time
|
|
)
|
|
{
|
|
return (time-secBX) % secDay;
|
|
}
|
|
|
|
|
|
private void
|
|
ParseTD(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
AssertF(at == atOneTime || at == atMinTime || at == atMacTime);
|
|
|
|
if (*ppsz == pszMac ||
|
|
!FParsTd(**ppsz,
|
|
(at != atMacTime) ? &pad->tdMin : &pad->tdMac,
|
|
fFalse,
|
|
(at == atMinTime)
|
|
)
|
|
) {
|
|
Usage(pad); // fFirst only if min time
|
|
}
|
|
|
|
++*ppsz;
|
|
}
|
|
|
|
|
|
private void
|
|
ParseTRange(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
AssertF(at == atTimeRange);
|
|
|
|
ParseTD(pad, atMinTime, ppsz, pszMac);
|
|
|
|
// Second time is optional; parse it only if it is a valid time.
|
|
if (*ppsz < pszMac && FTryParseTd(**ppsz, &pad->tdMac, fFalse, fFalse))
|
|
ParseTD(pad, atMacTime, ppsz, pszMac);
|
|
else
|
|
AssertF(FParsTd(".", &pad->tdMac, fFalse, fFalse));
|
|
}
|
|
|
|
|
|
// parses, starting at **ppsz, a decimal number
|
|
private void
|
|
ParseINum(
|
|
short *pw,
|
|
char ***ppsz,
|
|
char **pszMac,
|
|
int fDotIsOne
|
|
)
|
|
{
|
|
char **psz = *ppsz;
|
|
register char *pch;
|
|
int w;
|
|
|
|
AssertF(psz < pszMac);
|
|
|
|
pch = *psz;
|
|
if (*pch == '.' && *(pch+1) == '\0')
|
|
*pw = fDotIsOne ? 1 : 32766; // 32766 to prevent wrap
|
|
else {
|
|
pch = PchGetW(pch, &w); // PchGetW expects an (int *)
|
|
if (*pch != '\0')
|
|
FatalError("improper integer: %s\n", *psz);
|
|
|
|
*pw = w;
|
|
}
|
|
(*ppsz)++;
|
|
}
|
|
|
|
|
|
// parses, starting at **ppsz, two decimal numbers into ileMac and ileMin
|
|
private void
|
|
ParseIRange(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
int w;
|
|
|
|
AssertF(at == atCountRange);
|
|
|
|
ParseINum(&pad->ileMac, ppsz, pszMac, fFalse);
|
|
|
|
// If no more, or if the next argument is not '.' or a number, assume it is 1.
|
|
|
|
if (*ppsz == pszMac || (***ppsz != '.' && *PchGetW(**ppsz, &w) != 0))
|
|
pad->ileMin = 1;
|
|
else
|
|
ParseINum(&pad->ileMin, ppsz, pszMac, fTrue);
|
|
|
|
++pad->ileMac; // change from Last to Mac
|
|
if (pad->ileMin >= pad->ileMac)
|
|
FatalError("count values overlap\n");
|
|
}
|
|
|
|
|
|
// parse "file kind" flag into pad->fk
|
|
private void
|
|
ParseKind(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
static char dnch[] = "btuvw";
|
|
static FK rgfk[] = { fkBinary, fkText, fkUnrec, fkVersion, fkUnicode };
|
|
char *pch = index(dnch, ***ppsz);
|
|
|
|
AssertF(at == atKind);
|
|
|
|
if (*ppsz == pszMac || strlen(**ppsz) > 1 || !***ppsz || !pch)
|
|
Usage(pad);
|
|
|
|
pad->fk = rgfk[pch - dnch];
|
|
++*ppsz;
|
|
}
|
|
|
|
|
|
// parse optional pattern in pad->szPattern
|
|
private void
|
|
ParsePat(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
AssertF(at == atOptPat);
|
|
|
|
if (*ppsz < pszMac && !FSwitchar(***ppsz) && FWildSz(**ppsz)) {
|
|
pad->szPattern = **ppsz;
|
|
++*ppsz;
|
|
}
|
|
}
|
|
|
|
|
|
// save dir in pad->pthODir
|
|
private void
|
|
ParseDir(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
int ichLast = 0;
|
|
|
|
AssertF(at == atDir);
|
|
|
|
if (*ppsz == pszMac)
|
|
Usage(pad);
|
|
else if (FWildSz(**ppsz)) {
|
|
Warn("output directory must not contain wildcards\n");
|
|
Usage(pad);
|
|
}
|
|
|
|
ConvToSlash(**ppsz);
|
|
|
|
if (***ppsz != '/') {
|
|
// If a relative path, prepend current directory to pthODir
|
|
GetCurPth(pad->pthODir);
|
|
ichLast = strlen(pad->pthODir);
|
|
if (pad->pthODir[ichLast] != '/')
|
|
PthCopy(pad->pthODir + ichLast++, "/");
|
|
}
|
|
|
|
PthCopy(pad->pthODir + ichLast, **ppsz);
|
|
|
|
if (!FPthLogicalSz(pad->pthODir, pad->pthODir))
|
|
Usage(pad);
|
|
|
|
++*ppsz;
|
|
}
|
|
|
|
|
|
// parse name of user
|
|
private void
|
|
ParseUName(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
AssertF(at == atUserName);
|
|
|
|
if (*ppsz == pszMac)
|
|
// must have at least one
|
|
Usage(pad);
|
|
|
|
NmCopySz(pad->nmUser, **ppsz, cchUserMax);
|
|
(*ppsz)++;
|
|
}
|
|
|
|
|
|
// parse string into pad->sz
|
|
private void
|
|
ParseSz(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
AssertF(at == atSz || at == atSzDirs);
|
|
|
|
if (*ppsz == pszMac)
|
|
Usage(pad);
|
|
|
|
pad->sz = **ppsz;
|
|
++*ppsz;
|
|
|
|
if (pad->pecmd->atExtra == atSzDirs)
|
|
ParseFiles(pad, atFiles, ppsz, pszMac);
|
|
else if (*ppsz != pszMac)
|
|
Usage(pad);
|
|
}
|
|
|
|
|
|
// parse 1 string into pad->sz (can be others after it)
|
|
private void
|
|
ParseSz1(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
AssertF(at == atSz1);
|
|
|
|
if (*ppsz == pszMac)
|
|
Usage(pad);
|
|
|
|
pad->sz1 = **ppsz;
|
|
++*ppsz;
|
|
}
|
|
|
|
|
|
// parse 1 string into pad->sz2 (can be others after it)
|
|
private void
|
|
ParseSz2(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
AssertF(at == atSz2);
|
|
|
|
if (*ppsz == pszMac)
|
|
Usage(pad);
|
|
|
|
pad->sz2 = **ppsz;
|
|
++*ppsz;
|
|
}
|
|
|
|
|
|
void
|
|
ParseHelp(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
AssertF(at == atHelp);
|
|
|
|
Unreferenced(ppsz);
|
|
Unreferenced(pszMac);
|
|
|
|
PrErr("%s %s\n", pad->pecmd->szCmd, pad->pecmd->szDesc);
|
|
|
|
// Print usage w/o exiting immediately
|
|
szOp = "Usage";
|
|
Error(pad->pecmd->szUsage, pad->pecmd->szCmd);
|
|
|
|
// pad->pecmd->szHelp is too long to pass through slm's output functions, so
|
|
// we use the standard library. We need "\r\n" because stderr is in binary mode.
|
|
|
|
fputs("Arguments:\r\n", stderr);
|
|
fputs(pad->pecmd->szHelp, stderr);
|
|
fputs("For more detailed online and printed documentation, "
|
|
"please email the TRIO or NUTS alias.", stderr);
|
|
|
|
ExitSlm();
|
|
}
|
|
|
|
|
|
// parse name of log file
|
|
private void
|
|
ParseLogFile(
|
|
AD *pad,
|
|
AT at,
|
|
char ***ppsz,
|
|
char **pszMac
|
|
)
|
|
{
|
|
AssertF(at == atLogFile);
|
|
|
|
if (*ppsz == pszMac)
|
|
// must have at least one
|
|
Usage(pad);
|
|
|
|
InitLogHandle(pad, **ppsz);
|
|
(*ppsz)++;
|
|
}
|
|
|
|
|
|
void
|
|
Usage(
|
|
AD *pad
|
|
)
|
|
{
|
|
szOp = "Usage"; // for the proper effect
|
|
FatalError(pad->pecmd->szUsage, pad->pecmd->szCmd);
|
|
}
|