summaryrefslogtreecommitdiffstats
path: root/private/net/svcdlls/at/atcmd
diff options
context:
space:
mode:
Diffstat (limited to 'private/net/svcdlls/at/atcmd')
-rw-r--r--private/net/svcdlls/at/atcmd/at.rc12
-rw-r--r--private/net/svcdlls/at/atcmd/atcmd.c2290
-rw-r--r--private/net/svcdlls/at/atcmd/lmatmsg.mc193
-rw-r--r--private/net/svcdlls/at/atcmd/makefile6
-rw-r--r--private/net/svcdlls/at/atcmd/makefile.inc4
-rw-r--r--private/net/svcdlls/at/atcmd/sources32
6 files changed, 2537 insertions, 0 deletions
diff --git a/private/net/svcdlls/at/atcmd/at.rc b/private/net/svcdlls/at/atcmd/at.rc
new file mode 100644
index 000000000..22e35e70a
--- /dev/null
+++ b/private/net/svcdlls/at/atcmd/at.rc
@@ -0,0 +1,12 @@
+#include <windows.h>
+#include <ntverp.h>
+
+#define VER_FILETYPE VFT_APP
+#define VER_FILESUBTYPE VFT2_UNKNOWN
+#define VER_FILEDESCRIPTION_STR "Schedule service command line interface"
+#define VER_INTERNALNAME_STR "AT.EXE"
+#define VER_ORIGINALFILENAME_STR "AT.EXE"
+
+#include "common.ver"
+#include "lmatmsg.rc"
+
diff --git a/private/net/svcdlls/at/atcmd/atcmd.c b/private/net/svcdlls/at/atcmd/atcmd.c
new file mode 100644
index 000000000..29dc9455d
--- /dev/null
+++ b/private/net/svcdlls/at/atcmd/atcmd.c
@@ -0,0 +1,2290 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ atcmd.c
+
+Abstract:
+
+ Code for AT command, to be used with SCHEDULE service on Windows NT.
+
+ The module was taken from LanManager\at.c and then modified considerably
+ to work with NT Schedule service.
+
+Author:
+
+ Vladimir Z. Vulovic (vladimv) 06 - November - 1992
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+ 06-Nov-1992 vladimv
+ Created
+
+ 20-Feb-1993 yihsins
+ Get rid of hard coded strings and parse/print time according
+ to user profile
+
+ 25-May-1993 RonaldM
+ Convert strings to OEM before printing to the console, since
+ the console doesn't yet support unicode.
+
+ 28-Jun-1993 RonaldM
+ Added the "confirm" yes and no strings, which are meant to be
+ localised. The original yes and no strings cannot be localised,
+ because this would create batch file incompatibilities.
+
+ 07-Jul-1994 vladimv
+ Added support for interactive switch. Replaced "confirm" strings
+ with APE2_GEN_* strings - to eliminate redundancy. The rule is
+ that switches are not internationalizable while switch values are.
+
+--*/
+
+#include <nt.h> // DbgPrint prototype
+#include <ntrtl.h> // DbgPrint
+#include <nturtl.h> // Needed by winbase.h
+
+#include <windows.h>
+#include <winnls.h>
+#include <shellapi.h>
+
+#include <lmcons.h> // NET_API_STATUS
+#include <lmerr.h> // NetError codes
+#include <icanon.h> // NetpNameValidate
+
+#include "lmatmsg.h" // private AT error codes & messages
+#include <lmat.h> // AT_INFO
+#include <stdlib.h> // exit()
+#include <stdio.h> // printf
+#include <wchar.h> // wcslen
+#include <apperr.h> // APE_AT_USAGE
+#include <apperr2.h> // APE2_GEN_MONDAY + APE2_*
+#include <lmapibuf.h> // NetApiBufferFree
+#include <timelib.h> // NetpGetTimeFormat
+#include <luidate.h> // LUI_ParseTimeSinceStartOfDay
+
+
+#define YES_FLAG 0
+#define NO_FLAG 1
+#define INVALID_FLAG -1
+
+#define DUMP_ALL 0
+#define DUMP_ID 1
+#define ADD_TO_SCHEDULE 2
+#define ADD_ONETIME 3
+#define DEL_ID 4
+#define DEL_ALL 5
+#define ACTION_USAGE 6
+#define MAX_COMMAND_LEN 128
+#define MAX_SCHED_FIELD_LENGTH 24
+
+
+#define PutNewLine() GenOutput( TEXT("\n"))
+#define PutNewLine2() GenOutput( TEXT("\n\n"))
+
+#define MAX_MSG_BUFFER 1024
+
+WCHAR ConBuf[MAX_MSG_BUFFER];
+
+#define GenOutput(fmt) \
+ {wcscpy(ConBuf, fmt); \
+ ConsolePrint(ConBuf, wcslen(ConBuf));}
+
+#define GenOutputArg(fmt, a1) \
+ {wsprintf(ConBuf, fmt, a1); \
+ ConsolePrint(ConBuf, wcslen(ConBuf));}
+//
+// Formats used by printf.
+//
+#define DUMP_FMT1 TEXT("%-7.7ws")
+
+//
+// DUMP_FMT2 is chosen so that the most common case (id numbers less than 100)
+// looks good: two spaces for a number, three spaces for blanks.
+// Larger numbers just like in LM21 will result to shifted display.
+//
+#define DUMP_FMT2 TEXT("%2d ")
+#define MAX_TIME_FIELD_LENGTH 14
+#define DUMP_FMT3 TEXT("%ws") // for printing JobTime
+
+#define NULLC L'\0'
+#define BLANK L' '
+#define SLASH L'/'
+#define BACKSLASH L'\\'
+#define ELLIPSIS L"..."
+
+#define QUESTION_SW L"/?"
+#define QUESTION_SW_TOO L"-?"
+#define SCHED_TOK_DELIM L"," // string of valid delimiters for days & dates
+#define ARG_SEP_CHR L':'
+
+typedef struct _SEARCH_LIST {
+ WCHAR * String;
+ DWORD MessageId;
+ DWORD Value;
+} SEARCH_LIST, *PSEARCH_LIST, *LPSEARCH_LIST;
+
+//
+// All of the values below must be bitmasks. MatchString() depends on that!
+//
+#define AT_YES_VALUE 0x0001
+#define AT_DELETE_VALUE 0x0002
+#define AT_EVERY_VALUE 0x0004
+#define AT_NEXT_VALUE 0x0008
+#define AT_NO_VALUE 0x0010
+#define AT_CONFIRM_YES_VALUE 0x0020
+#define AT_CONFIRM_NO_VALUE 0x0040
+#define AT_INTERACTIVE 0x0080
+
+SEARCH_LIST GlobalListTable[] = {
+ { NULL, IDS_YES, AT_YES_VALUE},
+ { NULL, IDS_DELETE, AT_DELETE_VALUE},
+ { NULL, IDS_EVERY, AT_EVERY_VALUE},
+ { NULL, IDS_NEXT, AT_NEXT_VALUE},
+ { NULL, IDS_NO, AT_NO_VALUE},
+ { NULL, APE2_GEN_YES, AT_CONFIRM_YES_VALUE},
+ { NULL, APE2_GEN_NO, AT_CONFIRM_NO_VALUE},
+ { NULL, IDS_INTERACTIVE, AT_INTERACTIVE},
+ { NULL, 0, 0 }
+};
+
+SEARCH_LIST DaysOfWeekSearchList[] = {
+ { NULL, APE2_GEN_MONDAY_ABBREV, 0},
+ { NULL, APE2_GEN_TUESDAY_ABBREV, 1},
+ { NULL, APE2_GEN_WEDNSDAY_ABBREV, 2},
+ { NULL, APE2_GEN_THURSDAY_ABBREV, 3},
+ { NULL, APE2_GEN_FRIDAY_ABBREV, 4},
+ { NULL, APE2_GEN_SATURDAY_ABBREV, 5},
+ { NULL, APE2_TIME_SATURDAY_ABBREV2, 5},
+ { NULL, APE2_GEN_SUNDAY_ABBREV, 6},
+ { NULL, APE2_GEN_MONDAY, 0},
+ { NULL, APE2_GEN_TUESDAY, 1},
+ { NULL, APE2_GEN_WEDNSDAY, 2},
+ { NULL, APE2_GEN_THURSDAY, 3},
+ { NULL, APE2_GEN_FRIDAY, 4},
+ { NULL, APE2_GEN_SATURDAY, 5},
+ { NULL, APE2_GEN_SUNDAY, 6},
+ { NULL, APE2_GEN_NONLOCALIZED_MONDAY_ABBREV, 0},
+ { NULL, APE2_GEN_NONLOCALIZED_TUESDAY_ABBREV, 1},
+ { NULL, APE2_GEN_NONLOCALIZED_WEDNSDAY_ABBREV, 2},
+ { NULL, APE2_GEN_NONLOCALIZED_THURSDAY_ABBREV, 3},
+ { NULL, APE2_GEN_NONLOCALIZED_FRIDAY_ABBREV, 4},
+ { NULL, APE2_GEN_NONLOCALIZED_SATURDAY_ABBREV, 5},
+ { NULL, APE2_GEN_NONLOCALIZED_SATURDAY_ABBREV2, 5},
+ { NULL, APE2_GEN_NONLOCALIZED_SUNDAY_ABBREV, 6},
+ { NULL, APE2_GEN_NONLOCALIZED_MONDAY, 0},
+ { NULL, APE2_GEN_NONLOCALIZED_TUESDAY, 1},
+ { NULL, APE2_GEN_NONLOCALIZED_WEDNSDAY, 2},
+ { NULL, APE2_GEN_NONLOCALIZED_THURSDAY, 3},
+ { NULL, APE2_GEN_NONLOCALIZED_FRIDAY, 4},
+ { NULL, APE2_GEN_NONLOCALIZED_SATURDAY, 5},
+ { NULL, APE2_GEN_NONLOCALIZED_SUNDAY, 6},
+ { NULL, 0, 0 }
+ };
+
+BOOL
+AreYouSure(
+ VOID
+ );
+BOOL
+ArgIsServerName(
+ WCHAR * string
+ );
+BOOL
+ArgIsTime(
+ IN WCHAR * timestr,
+ OUT PDWORD pJobTime
+ );
+BOOL
+ArgIsDecimalString(
+ IN WCHAR * pDecimalString,
+ OUT PDWORD pNumber
+ );
+DWORD
+ConsolePrint(
+ IN LPWSTR pch,
+ IN int cch
+ );
+int
+FileIsConsole(
+ int fh
+ );
+BOOL
+IsDayOfMonth(
+ IN WCHAR * pToken,
+ OUT PDWORD pDay
+ );
+BOOL
+IsDayOfWeek(
+ IN WCHAR * pToken,
+ OUT PDWORD pDay
+ );
+NET_API_STATUS
+JobAdd(
+ VOID
+ );
+NET_API_STATUS
+JobEnum(
+ VOID
+ );
+NET_API_STATUS
+JobGetInfo(
+ VOID
+ );
+DWORD
+MatchString(
+ WCHAR * name,
+ DWORD mask
+ );
+DWORD
+MessageGet(
+ IN DWORD MessageId,
+ IN LPWSTR *buffer,
+ IN DWORD Size
+ );
+DWORD
+MessagePrint(
+ IN DWORD MessageId,
+ ...
+ );
+BOOL
+ParseJobIdArgs(
+ WCHAR ** argv,
+ int argc,
+ int argno,
+ PBOOL pDeleteFound
+ );
+BOOL
+ParseTimeArgs(
+ WCHAR ** argv,
+ int argc,
+ int argno,
+ int * pargno
+ );
+VOID
+PrintDay(
+ int type,
+ DWORD DaysOfMonth,
+ UCHAR DaysOfWeek,
+ UCHAR Flags
+ );
+VOID
+PrintLine(
+ VOID
+ );
+VOID
+PrintTime(
+ DWORD JobTime
+ );
+BOOL
+TraverseSearchList(
+ PWCHAR String,
+ PSEARCH_LIST SearchList,
+ PDWORD pValue
+ );
+VOID
+Usage(
+ BOOL GoodCommand
+ );
+BOOL
+ValidateCommand(
+ IN int argc,
+ IN WCHAR ** argv,
+ OUT int * pCommand
+ );
+
+VOID
+GetTimeString(
+ DWORD Time,
+ WCHAR *Buffer,
+ int BufferLength
+ );
+
+BOOL
+InitList(
+ PSEARCH_LIST SearchList
+ );
+
+VOID
+TermList(
+ PSEARCH_LIST SearchList
+ );
+
+DWORD
+GetStringColumn(
+ WCHAR *
+ );
+
+AT_INFO GlobalAtInfo; // buffer for scheduling new jobs
+WCHAR GlobalAtInfoCommand[ MAX_COMMAND_LEN + 1];
+
+DWORD GlobalJobId; // id of job in question
+PWSTR GlobalServerName;
+HANDLE GlobalMessageHandle;
+BOOL GlobalYes;
+BOOL GlobalDeleteAll;
+BOOL GlobalErrorReported;
+BOOL bDBCS;
+
+CHAR ** GlobalCharArgv; // keeps original input
+
+NET_TIME_FORMAT GlobalTimeFormat = {0};
+
+// In OS/2 it used to be OK to call "exit()" with a negative number. In
+// NT however, "exit()" should be called with a positive number only (a
+// valid windows error code?!). Note that OS/2 AT command used to call
+// exit(+1) for bad user input, and exit(-1) where -1 would get mapped to
+// 255 for other errors. To keep things simple and to avoid calling exit()
+// with a negative number, NT AT command calls exit(+1) for all possible
+// errors.
+
+#define AT_GENERIC_ERROR 1
+
+
+VOID _CRTAPI1
+main(
+ int argc,
+ CHAR ** charArgv
+ )
+/*++
+
+Routine Description:
+
+ Main module. Note that strings (for now) arrive as asciiz even
+ if you compile app for UNICODE.
+
+Arguments:
+
+ argc - argument count
+ charArgv - array of ascii strings
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NET_API_STATUS status = NERR_Success;
+ int command; // what to do
+ WCHAR ** argv;
+ DWORD cp;
+ CPINFO CurrentCPInfo;
+
+ GlobalYes = FALSE;
+ GlobalDeleteAll = FALSE;
+ GlobalErrorReported = FALSE;
+ GlobalCharArgv = charArgv;
+
+ /*
+ Added for bilingual message support. This is needed for FormatMessage
+ to work correctly. (Called from DosGetMessage).
+ Get current CodePage Info. We need this to decide whether
+ or not to use half-width characters.
+ */
+
+
+ GetCPInfo(cp=GetConsoleOutputCP(), &CurrentCPInfo);
+ switch ( cp ) {
+ case 932:
+ case 936:
+ case 949:
+ case 950:
+ SetThreadLocale(
+ MAKELCID(
+ MAKELANGID(
+ PRIMARYLANGID(GetSystemDefaultLangID()),
+ SUBLANG_ENGLISH_US ),
+ SORT_DEFAULT
+ )
+ );
+ bDBCS = TRUE;
+ break;
+
+ default:
+ SetThreadLocale(
+ MAKELCID(
+ MAKELANGID( LANG_ENGLISH, SUBLANG_ENGLISH_US ),
+ SORT_DEFAULT
+ )
+ );
+ bDBCS = FALSE;
+ break;
+ }
+
+ GlobalMessageHandle = LoadLibrary( L"netmsg.dll");
+ if ( GlobalMessageHandle == NULL) {
+ MessagePrint( IDS_LOAD_LIBRARY_FAILURE, GetLastError());
+ exit( AT_GENERIC_ERROR);
+ }
+
+ if ( ( argv = CommandLineToArgvW( GetCommandLineW(), &argc)) == NULL) {
+ MessagePrint( IDS_UNABLE_TO_MAP_TO_UNICODE );
+ exit( AT_GENERIC_ERROR);
+ }
+
+ if ( ValidateCommand( argc, argv, &command) == FALSE) {
+ Usage( FALSE);
+ exit( AT_GENERIC_ERROR);
+ }
+
+ switch( command) {
+
+ case DUMP_ALL:
+ status = JobEnum();
+ break;
+
+ case DUMP_ID:
+ status = JobGetInfo();
+ break;
+
+ case ADD_TO_SCHEDULE:
+ status = JobAdd();
+ break;
+
+ case DEL_ALL:
+ if ( AreYouSure() == FALSE) {
+ break;
+ }
+ status = NetScheduleJobDel(
+ GlobalServerName,
+ 0,
+ (DWORD)-1
+ );
+ if ( status == NERR_Success || status == APE_AT_ID_NOT_FOUND) {
+ break;
+ }
+ MessagePrint( status );
+ break;
+
+ case DEL_ID:
+ status = NetScheduleJobDel(
+ GlobalServerName,
+ GlobalJobId,
+ GlobalJobId
+ );
+ if ( status == NERR_Success) {
+ break;
+ }
+ MessagePrint( status );
+ break;
+
+ case ACTION_USAGE:
+ Usage( TRUE);
+ status = NERR_Success;
+ break;
+ }
+
+ TermList( GlobalListTable);
+ TermList( DaysOfWeekSearchList);
+ LocalFree( GlobalTimeFormat.AMString );
+ LocalFree( GlobalTimeFormat.PMString );
+ LocalFree( GlobalTimeFormat.DateFormat );
+ LocalFree( GlobalTimeFormat.TimeSeparator );
+ exit( status == NERR_Success ? ERROR_SUCCESS : AT_GENERIC_ERROR);
+}
+
+
+
+BOOL
+AreYouSure(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Make sure user really wants to delete all jobs.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ TRUE if user really wants to go ahead.
+ FALSE otherwise.
+
+--*/
+{
+ register int retries = 0;
+ WCHAR rbuf[ 16];
+ WCHAR * smallBuffer = NULL;
+ DWORD Value;
+ int cch;
+ int retc;
+
+ if ( GlobalYes == TRUE) {
+ return( TRUE);
+ }
+
+ if ( MessagePrint( APE2_AT_DEL_WARNING ) == 0) {
+ exit( AT_GENERIC_ERROR);
+ }
+
+ for ( ; ;) {
+
+ if ( MessageGet(
+ APE2_GEN_DEFAULT_NO, // MessageId
+ &smallBuffer, // lpBuffer
+ 0
+ ) == 0) {
+ exit( AT_GENERIC_ERROR);
+ }
+
+ if ( MessagePrint( APE_OkToProceed, smallBuffer) == 0) {
+ exit( AT_GENERIC_ERROR);
+ }
+
+ LocalFree( smallBuffer );
+
+ if (FileIsConsole(STD_INPUT_HANDLE)) {
+ retc = ReadConsole(GetStdHandle(STD_INPUT_HANDLE),rbuf,16,&cch,0);
+ if (retc) {
+ //
+ // Get rid of cr/lf
+ //
+ if (wcschr(rbuf, TEXT('\r')) == NULL) {
+ if (wcschr(rbuf, TEXT('\n')))
+ *wcschr(rbuf, TEXT('\n')) = NULLC;
+ }
+ else
+ *wcschr(rbuf, TEXT('\r')) = NULLC;
+ }
+ }
+ else {
+ CHAR oemBuf[ 16 ];
+
+ retc = (int)gets(oemBuf);
+#if DBG
+ fprintf(stderr, "got >%s<\n", oemBuf);
+#endif
+ cch = 0;
+ if (retc || strlen(oemBuf)+1 > 16)
+ cch = MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED,
+ oemBuf, strlen(oemBuf)+1, rbuf, 16);
+ }
+
+#if DBG
+ fprintf(stderr, "cch = %d, retc = %d\n", cch, retc);
+#endif
+ if (!retc || cch == 0)
+ return( FALSE);
+#if DBG
+ fprintf(stderr, "converted to >%ws<\n", rbuf);
+#endif
+
+ Value = MatchString(_wcsupr(rbuf), AT_CONFIRM_NO_VALUE | AT_CONFIRM_YES_VALUE);
+
+ if ( Value == AT_CONFIRM_NO_VALUE) {
+ return( FALSE);
+ } else if ( Value == AT_CONFIRM_YES_VALUE) {
+ break;
+ }
+
+ if ( ++retries >= 3) {
+ MessagePrint( APE_NoGoodResponse );
+ return( FALSE);
+ }
+
+ if ( MessagePrint( APE_UtilInvalidResponse ) == 0) {
+ exit( AT_GENERIC_ERROR);
+ }
+ }
+ return( TRUE);
+}
+
+
+
+BOOL
+ArgIsServerName(
+ WCHAR * string
+ )
+/*++
+
+Routine Description:
+
+ Checks if string is a server name. Validation is really primitive, eg
+ strings like "\\\threeslashes" pass the test.
+
+Arguments:
+
+ string - pointer to string that may represent a server name
+
+Return Value:
+
+ TRUE - string is (or might be) a valid server name
+ FALSE - string is not a valid server name
+
+--*/
+{
+
+ NET_API_STATUS ApiStatus;
+
+ if (string[0] == BACKSLASH && string[1] == BACKSLASH && string[2] != 0) {
+ ApiStatus = NetpNameValidate(
+ NULL, // no server name.
+ &string[2], // name to validate
+ NAMETYPE_COMPUTER,
+ LM2X_COMPATIBLE); // flags
+ if (ApiStatus != NO_ERROR) {
+ return (FALSE);
+ }
+ GlobalServerName = string;
+ return( TRUE);
+ }
+
+ return( FALSE); // GlobalServerName is NULL at load time
+}
+
+
+
+BOOL
+ArgIsTime(
+ IN WCHAR * timestr,
+ OUT PDWORD pJobTime
+ )
+/*++
+
+Routine Description:
+
+ Determines whether string is a time or not. Validates that string
+ passed into it is in the form of HH:MM. It searches the string for
+ a ":" and then validates that the preceeding data is numeric & in a
+ valid range for hours. It then validates the string after the ":"
+ is numeric & in a validate range for minutes. If all the tests are
+ passed the TRUE is returned.
+
+Arguments:
+
+ timestr - string to check whether it is a time
+ JobTime - ptr to number of miliseconds
+
+Return Value:
+
+ TRUE - timestr was a time in HH:MM format
+ FALSE - timestr wasn't at time
+
+--*/
+{
+ CHAR buffer[MAX_TIME_SIZE];
+ USHORT ParseLen;
+ BOOL fDummy;
+
+ if ( timestr == NULL )
+ return FALSE;
+
+ if ( !WideCharToMultiByte( CP_ACP,
+ 0,
+ timestr,
+ -1,
+ buffer,
+ sizeof( buffer )/sizeof(CHAR),
+ NULL,
+ &fDummy ))
+ {
+ return FALSE;
+ }
+
+ if ( LUI_ParseTimeSinceStartOfDay( buffer, pJobTime, &ParseLen, 0) )
+ return FALSE;
+
+ // LUI_ParseTimeSinceStartOfDay returns the time in seconds.
+ // Hence, we need to convert it to microseconds.
+ *pJobTime *= 1000;
+
+ return( TRUE);
+}
+
+
+
+BOOL
+ArgIsDecimalString(
+ IN WCHAR * pDecimalString,
+ OUT PDWORD pNumber
+ )
+/*++
+
+Routine Description:
+
+ This routine converts a string into a DWORD if it possibly can.
+ The conversion is successful if string is decimal numeric and
+ does not lead to an overflow.
+
+Arguments:
+
+ pDecimalString ptr to decimal string
+ pNumber ptr to number
+
+Return Value:
+
+ FALSE invalid number
+ TRUE valid number
+
+--*/
+{
+ DWORD Value;
+ DWORD OldValue;
+ DWORD digit;
+
+ if ( pDecimalString == NULL || *pDecimalString == 0) {
+ return( FALSE);
+ }
+
+ Value = 0;
+
+ while ( (digit = *pDecimalString++) != 0) {
+
+ if ( digit < L'0' || digit > L'9') {
+ return( FALSE); // not a decimal string
+ }
+
+ OldValue = Value;
+ Value = digit - L'0' + 10 * Value;
+ if ( Value < OldValue) {
+ return( FALSE); // overflow
+ }
+ }
+
+ *pNumber = Value;
+ return( TRUE);
+}
+
+
+
+BOOL
+IsDayOfMonth(
+ IN WCHAR * pToken,
+ OUT PDWORD pDay
+ )
+/*++
+
+Routine Description:
+
+ Converts a string into a number for the day of the month, if it can
+ possibly do so. Note that "first" == 1, ...
+
+Arguments:
+
+ pToken pointer to schedule token for the day of the month
+ pDay pointer to index of day in a month
+
+Return Value:
+
+ TRUE if a valid schedule token
+ FALSE otherwise
+
+--*/
+{
+ return ( ArgIsDecimalString( pToken, pDay) == TRUE && *pDay >= 1
+ && *pDay <= 31);
+}
+
+
+
+BOOL
+IsDayOfWeek(
+ WCHAR * pToken,
+ PDWORD pDay
+ )
+/*++
+
+Routine Description:
+
+ This routine converts a string day of the week into a integer
+ offset into the week if it possibly can. Note that Monday==0,
+ ..., Sunday == 6.
+
+Arguments:
+
+ pToken pointer to schedule token for the day of a week
+ pDay pointer to index of day in a month
+
+Return Value:
+
+ TRUE if a valid schedule token
+ FALSE otherwise
+
+--*/
+{
+ if ( !InitList( DaysOfWeekSearchList ) )
+ {
+ // Error already reported
+ exit( -1 );
+ }
+
+ return( TraverseSearchList(
+ pToken,
+ DaysOfWeekSearchList,
+ pDay
+ ));
+}
+
+
+
+NET_API_STATUS
+JobAdd(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Adds a new item to schedule.
+
+Arguments:
+
+ None. Uses globals.
+
+Return Value:
+
+ NET_API_STATUS return value of remote api call
+
+--*/
+{
+ NET_API_STATUS status;
+
+ for ( ; ; ) {
+ status = NetScheduleJobAdd(
+ GlobalServerName,
+ (LPBYTE)&GlobalAtInfo,
+ &GlobalJobId
+ );
+ if ( status == ERROR_INVALID_PARAMETER &&
+ GlobalAtInfo.Flags & JOB_NONINTERACTIVE) {
+ //
+ // We may have failed because we are talking to a down level
+ // server that does not know about JOB_NONINTERACTIVE bit.
+ // Clear the bit, and try again.
+ // A better approach would be to check the version of the
+ // server before making NetScheduleJobAdd() call, adjust the
+ // bit appropriately and only then call NetScheduleJobAdd().
+ //
+ GlobalAtInfo.Flags &= ~JOB_NONINTERACTIVE;
+ } else {
+ break;
+ }
+
+ }
+ if ( status == NERR_Success) {
+ MessagePrint( IDS_ADD_NEW_JOB, GlobalJobId );
+ } else {
+ if ( MessagePrint( status ) == 0) {
+ exit( AT_GENERIC_ERROR);
+ }
+ }
+
+ return( status);
+}
+
+
+
+NET_API_STATUS
+JobEnum(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This does all of the processing necessary to dump out the entire
+ schedule file. It loops through on each record and formats its
+ information for printing and then goes to the next.
+
+Arguments:
+
+ None. Uses globals.
+
+Return Value:
+
+ ERROR_SUCCESS if everything enumerated OK
+ error returned by remote api otherwise
+
+--*/
+{
+ BOOL first = TRUE;
+ DWORD ResumeJobId = 0;
+ NET_API_STATUS status = NERR_Success;
+ PAT_ENUM pAtEnum;
+ DWORD EntriesRead;
+ DWORD TotalEntries;
+ LPVOID EnumBuffer;
+ DWORD length;
+ WCHAR * smallBuffer = NULL;
+
+ for ( ; ;) {
+
+ status = NetScheduleJobEnum(
+ GlobalServerName,
+ (LPBYTE *)&EnumBuffer,
+ (DWORD)-1,
+ &EntriesRead,
+ &TotalEntries,
+ &ResumeJobId
+ );
+
+ if ( status != ERROR_SUCCESS && status != ERROR_MORE_DATA) {
+ length = MessagePrint( status );
+ if ( length == 0) {
+ exit( AT_GENERIC_ERROR);
+ }
+ return( status);
+ }
+
+ ASSERT( status == ERROR_SUCCESS ? TotalEntries == EntriesRead
+ : TotalEntries > EntriesRead);
+
+ if ( TotalEntries == 0) {
+ break; // no items found
+ }
+
+ if ( first == TRUE) {
+ length = MessagePrint( APE2_AT_DUMP_HEADER );
+ if ( length == 0) {
+ exit( AT_GENERIC_ERROR);
+ }
+ PrintLine(); // line across screen
+ first = FALSE;
+ }
+
+ for ( pAtEnum = EnumBuffer; EntriesRead-- > 0; pAtEnum++) {
+ if ( pAtEnum->Flags & JOB_EXEC_ERROR) {
+ if ( MessageGet( APE2_GEN_ERROR, &smallBuffer, 0 ) == 0) {
+ // error reported already
+ exit( AT_GENERIC_ERROR);
+ }
+ GenOutputArg( DUMP_FMT1, smallBuffer );
+ LocalFree( smallBuffer );
+ } else {
+ GenOutputArg( DUMP_FMT1, L"");
+ }
+ GenOutputArg( DUMP_FMT2, pAtEnum->JobId);
+ PrintDay( DUMP_ALL, pAtEnum->DaysOfMonth, pAtEnum->DaysOfWeek,
+ pAtEnum->Flags);
+ PrintTime( pAtEnum->JobTime);
+ GenOutputArg( TEXT("%ws\n"), pAtEnum->Command);
+ }
+
+ if ( EnumBuffer != NULL) {
+ (VOID)NetApiBufferFree( (LPVOID)EnumBuffer);
+ EnumBuffer = NULL;
+ }
+
+ if ( status == ERROR_SUCCESS) {
+ break; // we have read & displayed all the items
+ }
+ }
+
+ if ( first == TRUE) {
+ MessagePrint( APE_EmptyList );
+ }
+
+ return( ERROR_SUCCESS);
+}
+
+
+
+NET_API_STATUS
+JobGetInfo(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This prints out the schedule of an individual items schedule.
+
+Arguments:
+
+ None. Uses globals.
+
+Return Value:
+
+ NET_API_STATUS value returned by remote api
+
+--*/
+
+{
+ PAT_INFO pAtInfo = NULL;
+ NET_API_STATUS status;
+
+ status = NetScheduleJobGetInfo(
+ GlobalServerName,
+ GlobalJobId,
+ (LPBYTE *)&pAtInfo
+ );
+ if ( status != NERR_Success) {
+ MessagePrint( status );
+ return( status);
+ }
+
+ PutNewLine();
+ MessagePrint( APE2_AT_DI_TASK );
+ GenOutputArg( TEXT("%d"), GlobalJobId);
+ PutNewLine();
+
+ MessagePrint( APE2_AT_DI_STATUS );
+ MessagePrint( (pAtInfo->Flags & JOB_EXEC_ERROR) != 0 ?
+ APE2_GEN_ERROR : APE2_GEN_OK );
+ PutNewLine();
+
+ MessagePrint( APE2_AT_DI_SCHEDULE );
+ PrintDay( DUMP_ID, pAtInfo->DaysOfMonth, pAtInfo->DaysOfWeek,
+ pAtInfo->Flags);
+ PutNewLine();
+
+ MessagePrint( APE2_AT_DI_TIMEOFDAY );
+ PrintTime( pAtInfo->JobTime);
+ PutNewLine();
+
+ MessagePrint( APE2_AT_DI_INTERACTIVE);
+ MessagePrint( (pAtInfo->Flags & JOB_NONINTERACTIVE) == 0 ?
+ APE2_GEN_YES : APE2_GEN_NO );
+ PutNewLine();
+
+ MessagePrint( APE2_AT_DI_COMMAND );
+ GenOutputArg( TEXT("%ws\n"), pAtInfo->Command);
+ PutNewLine2();
+
+ (VOID)NetApiBufferFree( (LPVOID)pAtInfo);
+
+ return( NERR_Success);
+}
+
+
+
+DWORD
+MatchString(
+ WCHAR * name,
+ DWORD Values
+ )
+/*++
+Routine Description:
+
+ Parses switch string and returns NULL for an invalid switch,
+ and -1 for an ambiguous switch.
+
+Arguments:
+
+ name - pointer to string we need to examine
+ Values - bitmask of values of interest
+
+Return Value:
+
+ Pointer to command, or NULL or -1.
+--*/
+{
+ WCHAR * String;
+ PSEARCH_LIST pCurrentList;
+ WCHAR * CurrentString;
+ DWORD FoundValue;
+ int nmatches;
+ int longest;
+
+ if ( !InitList( GlobalListTable ) )
+ {
+ // Error already reported
+ exit( -1 );
+ }
+
+ for ( pCurrentList = GlobalListTable,
+ longest = nmatches = 0,
+ FoundValue = 0;
+ (CurrentString = pCurrentList->String) != NULL;
+ pCurrentList++) {
+
+ if ( (Values & pCurrentList->Value) == 0) {
+ continue; // skip this List
+ }
+
+ for ( String = name; *String == *CurrentString++; String++) {
+ if ( *String == 0) {
+ return( pCurrentList->Value); // exact match
+ }
+ }
+
+ if ( !*String) {
+
+ if ( String - name > longest) {
+
+ longest = String - name;
+ nmatches = 1;
+ FoundValue = pCurrentList->Value;
+
+ } else if ( String - name == longest) {
+
+ nmatches++;
+ }
+ }
+ }
+
+ // 0 corresponds to no match at all (invalid List)
+ // while -1 corresponds to multiple match (ambiguous List).
+
+ if ( nmatches != 1) {
+ return ( (nmatches == 0) ? 0 : -1);
+ }
+
+ return( FoundValue);
+}
+
+
+DWORD
+MessageGet(
+ IN DWORD MessageId,
+ OUT LPWSTR *buffer,
+ IN DWORD Size
+ )
+/*++
+
+Routine Description:
+
+ Fills in the unicode message corresponding to a given message id,
+ provided that a message can be found and that it fits in a supplied
+ buffer.
+
+Arguments:
+
+ MessageId - message id
+ buffer - pointer to caller supplied buffer
+ Size - size (always in bytes) of supplied buffer,
+ If size is 0, buffer will be allocated by FormatMessage.
+
+Return Value:
+
+ Count of characters, not counting the terminating null character,
+ returned in the buffer. Zero return value indicates failure.
+
+--*/
+{
+ DWORD length;
+ LPVOID lpSource;
+ DWORD dwFlags;
+
+ if ( MessageId < NERR_BASE) {
+ //
+ // Get message from system.
+ //
+ lpSource = NULL; // redundant step according to FormatMessage() spec
+ dwFlags = FORMAT_MESSAGE_FROM_SYSTEM;
+
+ } else if ( ( MessageId >= APE2_AT_DEL_WARNING
+ && MessageId <= APE2_AT_DI_INTERACTIVE)
+ || ( MessageId >= IDS_LOAD_LIBRARY_FAILURE
+ && MessageId <= IDS_INTERACTIVE )) {
+ //
+ // Get message from this module.
+ //
+ lpSource = NULL;
+ dwFlags = FORMAT_MESSAGE_FROM_HMODULE;
+
+ } else {
+ //
+ // Get message from netmsg.dll.
+ //
+ lpSource = GlobalMessageHandle;
+ dwFlags = FORMAT_MESSAGE_FROM_HMODULE;
+ }
+
+ if ( Size == 0 )
+ dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER;
+
+ length = FormatMessage(
+ dwFlags, // dwFlags
+ lpSource, // lpSource
+ MessageId, // MessageId
+ 0, // dwLanguageId
+ (LPWSTR) buffer, // lpBuffer
+ Size, // nSize
+ NULL // lpArguments
+ );
+
+ if ( length == 0) {
+ MessagePrint( IDS_MESSAGE_GET_ERROR, MessageId, GetLastError());
+ }
+ return( length);
+
+} // MessageGet()
+
+
+
+int
+FileIsConsole(
+ int fh
+ )
+{
+ unsigned htype ;
+
+ htype = GetFileType(GetStdHandle(fh));
+ htype &= ~FILE_TYPE_REMOTE;
+ return htype == FILE_TYPE_CHAR;
+}
+
+
+
+DWORD
+ConsolePrint(
+ LPWSTR pch,
+ int cch
+ )
+{
+ int cchOut;
+ int err;
+ CHAR *pchOemBuffer;
+
+ if (FileIsConsole(STD_OUTPUT_HANDLE)) {
+ err = WriteConsole(
+ GetStdHandle(STD_OUTPUT_HANDLE),
+ pch, cch,
+ &cchOut, NULL);
+ if (!err || cchOut != cch)
+ goto try_again;
+ }
+ else if ( cch != 0) {
+try_again:
+ cchOut = WideCharToMultiByte(CP_OEMCP, 0, pch, cch, NULL, 0, NULL,NULL);
+ if (cchOut == 0)
+ return 0;
+
+ if ((pchOemBuffer = (CHAR *)malloc(cchOut)) != NULL) {
+ WideCharToMultiByte(CP_OEMCP, 0, pch, cch,
+ pchOemBuffer, cchOut, NULL, NULL);
+ WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
+ pchOemBuffer, cchOut, &cch, NULL);
+ free(pchOemBuffer);
+ }
+ }
+ return cchOut;
+}
+
+
+
+DWORD
+MessagePrint(
+ IN DWORD MessageId,
+ ...
+ )
+/*++
+
+Routine Description:
+
+ Finds the unicode message corresponding to the supplied message id,
+ merges it with caller supplied string(s), and prints the resulting
+ string.
+
+Arguments:
+
+ MessageId - message id
+
+Return Value:
+
+ Count of characters, not counting the terminating null character,
+ printed by this routine. Zero return value indicates failure.
+
+--*/
+{
+ va_list arglist;
+ WCHAR * buffer = NULL;
+ DWORD length;
+ LPVOID lpSource;
+ DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER;
+
+
+ va_start( arglist, MessageId );
+
+ if ( MessageId < NERR_BASE) {
+ //
+ // Get message from system.
+ //
+ lpSource = NULL; // redundant step according to FormatMessage() spec
+ dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM;
+
+ } else if ( ( MessageId >= APE2_AT_DEL_WARNING
+ && MessageId <= APE2_AT_DI_INTERACTIVE)
+ || ( MessageId >= IDS_LOAD_LIBRARY_FAILURE
+ && MessageId <= IDS_INTERACTIVE )) {
+ //
+ // Get message from this module.
+ //
+ lpSource = NULL;
+ dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
+
+ } else {
+ //
+ // Get message from netmsg.dll.
+ //
+ lpSource = GlobalMessageHandle;
+ dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
+ }
+
+ length = FormatMessage(
+ dwFlags, // dwFlags
+ lpSource, // lpSource
+ MessageId, // MessageId
+ 0L, // dwLanguageId
+ (LPTSTR)&buffer, // lpBuffer
+ 0, // size
+ &arglist // lpArguments
+ );
+
+ length = ConsolePrint(buffer, length);
+
+ LocalFree(buffer);
+
+ return( length);
+
+} // MessagePrint()
+
+
+
+BOOL
+ParseJobIdArgs(
+ WCHAR ** argv,
+ int argc,
+ int argno,
+ PBOOL pDeleteFound
+ )
+/*++
+
+Routine Description:
+
+ Parses arguments for commands containing JobId (these can be JobGetInfo
+ and JobDel commands). It loops through JobId arguments making sure that
+ we have at most one "yes-no" switch and at most one "delete" switch and
+ nothing else.
+
+Arguments:
+
+ argv argument list
+ argc number of arguments to parse
+ argno index of argument to begin parsing from
+ pDeleteFound did we find a delete switch or not
+
+Return Value:
+
+ FALSE invalid argument found
+ TRUE valid arguments
+
+--*/
+{
+ BOOL FoundDeleteSwitch;
+
+ for ( FoundDeleteSwitch = FALSE; argno < argc; argno++) {
+
+ WCHAR * argp;
+ DWORD length;
+ DWORD Value;
+
+ argp = argv[ argno];
+
+ if ( *argp++ != SLASH) {
+ return( FALSE); // not a switch
+ }
+
+ _wcsupr( argp);
+ length = wcslen( argp);
+
+ Value = MatchString( argp, AT_YES_VALUE | AT_DELETE_VALUE);
+
+ if ( Value == AT_YES_VALUE) {
+
+ if ( GlobalYes == TRUE) {
+ return( FALSE); // multiple instances of yes switch
+ }
+
+ GlobalYes = TRUE;
+ continue;
+ }
+
+ if ( Value == AT_DELETE_VALUE) {
+
+ if ( FoundDeleteSwitch == TRUE) {
+ return( FALSE); // duplicate delete switch
+ }
+ FoundDeleteSwitch = TRUE;
+ continue;
+ }
+
+ return( FALSE); // an unknown switch
+ }
+
+ *pDeleteFound = FoundDeleteSwitch;
+ return( TRUE);
+} // ParseJobIdArgs()
+
+
+
+BOOL
+ParseTimeArgs(
+ WCHAR ** argv,
+ int argc,
+ int argno,
+ int * pargno
+ )
+/*++
+
+Routine Description:
+
+ Parses arguments for command addition.
+
+Arguments:
+
+ argv argument list
+ argc count of args
+ argno index of the first arg to validate
+ pargno ptr to the index of the first non-switch arg
+
+Return Value:
+
+ TRUE all arguments are valid
+ FALSE otherwise
+
+--*/
+{
+ DWORD day_no; // day number for scheduling
+ DWORD NextCount = 0; // count of next switches
+ DWORD EveryCount = 0; // count of every switches
+ WCHAR * argp; // ptr to arg string
+ WCHAR * schedp; // work ptr to arg string
+ DWORD Value; // bitmask
+
+ for ( NOTHING; argno < argc; argno++) {
+
+ argp = argv[ argno];
+
+ if ( *argp++ != SLASH) {
+ break; // found non-switch, we are done
+ }
+
+
+ schedp = wcschr( argp, ARG_SEP_CHR);
+
+ if ( schedp == NULL) {
+ return( FALSE);
+ }
+
+ _wcsupr( argp); // upper case entire input, not just the switch name
+
+ *schedp = 0;
+
+ Value = MatchString( argp, AT_NEXT_VALUE | AT_EVERY_VALUE);
+
+ if ( Value == AT_NEXT_VALUE) {
+
+ NextCount++;
+
+ } else if ( Value == AT_EVERY_VALUE) {
+
+ EveryCount++;
+ GlobalAtInfo.Flags |= JOB_RUN_PERIODICALLY;
+
+ } else {
+
+ return( FALSE); // an unexpected switch
+ }
+
+ if ( NextCount + EveryCount > 1) {
+ return( FALSE); // repeated switch option
+ }
+
+ *schedp++ = ARG_SEP_CHR;
+
+ schedp = wcstok( schedp, SCHED_TOK_DELIM);
+
+ if ( schedp == NULL) {
+ GlobalAtInfo.Flags |= JOB_ADD_CURRENT_DATE;
+ continue;
+ }
+
+ while( schedp != NULL) {
+
+ if ( IsDayOfMonth( schedp, &day_no) == TRUE) {
+
+ GlobalAtInfo.DaysOfMonth |= (1 << (day_no - 1));
+
+ } else if ( IsDayOfWeek( schedp, &day_no) == TRUE) {
+
+ GlobalAtInfo.DaysOfWeek |= (1 << day_no);
+
+ } else {
+ MessagePrint( APE_InvalidSwitchArg );
+ GlobalErrorReported = TRUE;
+ return( FALSE);
+ }
+
+ schedp = wcstok( NULL, SCHED_TOK_DELIM);
+ }
+ }
+
+ if ( argno == argc) {
+ return( FALSE); // all switches, no command
+ }
+
+ *pargno = argno;
+ return( TRUE);
+}
+
+
+
+BOOL
+ParseInteractiveArg(
+ IN OUT WCHAR * argp
+ )
+/*++
+
+Routine Description:
+
+ Returns TRUE if argp is an interactive switch.
+
+--*/
+{
+ DWORD Value; // bitmask
+
+ if ( *argp++ != SLASH) {
+ return( FALSE); // not a switch
+ }
+
+ _wcsupr( argp); // all AT command switches can be safely uppercased
+
+ Value = MatchString( argp, AT_INTERACTIVE);
+
+ if ( Value == AT_INTERACTIVE) {
+ GlobalAtInfo.Flags &= ~JOB_NONINTERACTIVE; // clear noninteractive flag
+ return( TRUE);
+ }
+
+ return( FALSE); // some other switch
+}
+
+
+
+VOID
+PrintDay(
+ int type,
+ DWORD DaysOfMonth,
+ UCHAR DaysOfWeek,
+ UCHAR Flags
+ )
+/*++
+
+Routine Description:
+
+ Print out schedule days. This routine converts a schedule bit map
+ to the literals that represent the schedule.
+
+Arguments:
+
+ type whether this is for JobEnum or not
+ DaysOfMonth bitmask for days of month
+ DaysOfWeek bitmaks for days of week
+ Flags extra info about the job
+
+Return Value:
+
+ None.
+
+--*/
+{
+ int i;
+ WCHAR Buffer[ 128];
+ DWORD BufferLength;
+ DWORD Length;
+ DWORD TotalLength = 0;
+ DWORD TotalColumnLength = 0;
+ WCHAR * LastSpace;
+ DWORD MessageId;
+ BOOL OverFlow = TRUE;
+ static int Ape2GenWeekdayLong[] = {
+ APE2_GEN_MONDAY,
+ APE2_GEN_TUESDAY,
+ APE2_GEN_WEDNSDAY,
+ APE2_GEN_THURSDAY,
+ APE2_GEN_FRIDAY,
+ APE2_GEN_SATURDAY,
+ APE2_GEN_SUNDAY
+ };
+ static int Ape2GenWeekdayAbbrev[] = {
+ APE2_GEN_MONDAY_ABBREV,
+ APE2_GEN_TUESDAY_ABBREV,
+ APE2_GEN_WEDNSDAY_ABBREV,
+ APE2_GEN_THURSDAY_ABBREV,
+ APE2_GEN_FRIDAY_ABBREV,
+ APE2_GEN_SATURDAY_ABBREV,
+ APE2_GEN_SUNDAY_ABBREV
+ };
+
+ //
+ // Subtract 4 to guard against days of week or days of month overflow.
+ //
+ BufferLength = sizeof( Buffer)/ sizeof( WCHAR) - 4;
+ if ( type == DUMP_ALL && BufferLength > MAX_SCHED_FIELD_LENGTH) {
+ BufferLength = MAX_SCHED_FIELD_LENGTH;
+ }
+
+ //
+ // First do the descriptive bit (eg. EACH, NEXT, etc) with the days.
+ //
+
+ if ( Flags & JOB_RUN_PERIODICALLY) {
+
+ MessageId = APE2_AT_EACH;
+
+ } else if ( (DaysOfWeek != 0) || (DaysOfMonth != 0)) {
+
+ MessageId = APE2_AT_NEXT;
+
+ } else if ( Flags & JOB_RUNS_TODAY) {
+
+ MessageId = APE2_AT_TODAY;
+
+ } else {
+
+ MessageId = APE2_AT_TOMORROW;
+ }
+
+ Length = MessageGet(
+ MessageId,
+ (LPWSTR *) &Buffer[TotalLength],
+ BufferLength
+ );
+ if ( Length == 0) {
+ goto PrintDay_exit; // Assume this is due to lack of space
+ }
+ TotalColumnLength = GetStringColumn( &Buffer[TotalLength] );
+ TotalLength = Length;
+
+ if ( DaysOfWeek != 0) {
+
+ for ( i = 0; i < 7; i++) {
+
+ if ( ( DaysOfWeek & (1 << i)) != 0) {
+
+ if( bDBCS ) {
+ Length = MessageGet(
+ Ape2GenWeekdayLong[ i],
+ (LPWSTR *) &Buffer[TotalLength],
+ BufferLength - TotalLength
+ );
+ } else {
+ Length = MessageGet(
+ Ape2GenWeekdayAbbrev[ i],
+ (LPWSTR *) &Buffer[TotalLength],
+ BufferLength - TotalLength
+ );
+ }
+ if ( Length == 0) {
+ //
+ // Not enough room for WeekDay symbol
+ //
+ goto PrintDay_exit;
+
+ }
+ //
+ // Get how many columns will be needed for display.
+ //
+ TotalColumnLength += GetStringColumn( &Buffer[TotalLength] );
+
+ if ( TotalColumnLength >= BufferLength) {
+ //
+ // Not enough room for space following WeekDay symbol
+ //
+ goto PrintDay_exit;
+ }
+ TotalLength +=Length;
+ Buffer[ TotalLength++] = BLANK;
+ TotalColumnLength++;
+ }
+ }
+ }
+
+ if ( DaysOfMonth != 0) {
+
+ for ( i = 0; i < 31; i++) {
+
+ if ( ( DaysOfMonth & (1L << i)) != 0) {
+
+ Length = swprintf(
+ &Buffer[ TotalLength],
+ L"%d ",
+ i + 1
+ );
+ if ( TotalLength + Length > BufferLength) {
+ //
+ // Not enough room for MonthDay symbol followed by space
+ //
+ goto PrintDay_exit;
+ }
+ TotalLength +=Length;
+ TotalColumnLength +=Length;
+ }
+ }
+ }
+
+ OverFlow = FALSE;
+
+PrintDay_exit:
+
+ Buffer[ TotalLength] = NULLC;
+
+ if ( OverFlow == TRUE) {
+
+ if ( TotalLength > 0 && Buffer[ TotalLength - 1] == BLANK) {
+ //
+ // Eliminate trailing space if there is one.
+ //
+ Buffer[ TotalLength - 1] = NULLC;
+ }
+
+ //
+ // Then get rid of the rightmost token (or even whole thing).
+ //
+
+ LastSpace = wcsrchr( Buffer, BLANK);
+
+ wcscpy( LastSpace != NULL ? LastSpace : Buffer, ELLIPSIS);
+
+ TotalLength = wcslen( Buffer);
+
+ }
+
+ if ( type == DUMP_ALL) {
+ while( TotalColumnLength++ < MAX_SCHED_FIELD_LENGTH) {
+ Buffer[ TotalLength++] = BLANK;
+ }
+ Buffer[ TotalLength] = UNICODE_NULL;
+ }
+
+ GenOutputArg( TEXT("%ws"), Buffer);
+}
+
+
+VOID
+PrintLine(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Prints a line accross screen.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+Note:
+
+ BUGBUG Is this treatment valid for UniCode? See also LUI_PrintLine()
+ BUGBUG in ui\common\src\lui\lui\border.c
+
+--*/
+#define SINGLE_HORIZONTAL L'\x02d'
+#define SCREEN_WIDTH 79
+{
+ WCHAR string[ SCREEN_WIDTH + 1];
+ DWORD offset;
+
+
+ for ( offset = 0; offset < SCREEN_WIDTH; offset++) {
+ string[ offset] = SINGLE_HORIZONTAL;
+ }
+
+ string[ SCREEN_WIDTH] = NULLC;
+ GenOutputArg(TEXT("%ws\n"), string);
+}
+
+
+VOID
+PrintTime(
+ DWORD JobTime
+ )
+/*++
+
+Routine Description:
+
+ Prints time of a job in HH:MM{A,P}M format.
+
+Arguments:
+
+ JobTime - time in miliseconds (measured from midnight)
+
+Return Value:
+
+ None.
+
+Note:
+
+ BUGBUG this does not make sure that JobTime is within the bounds.
+ BUGBUG Also, there is nothing unicode about printing this output.
+
+--*/
+{
+ WCHAR Buffer[15];
+
+ GetTimeString( JobTime, Buffer, sizeof( Buffer)/sizeof( WCHAR) );
+ GenOutputArg( DUMP_FMT3, Buffer );
+}
+
+
+
+BOOL
+TraverseSearchList(
+ IN PWCHAR String,
+ IN PSEARCH_LIST SearchList,
+ OUT PDWORD pValue
+ )
+/*++
+
+Routine Description:
+
+ Examines search list until it find the correct entry, then returns
+ the value corresponding to this entry.
+
+Arguments:
+
+ String - string to match
+ SearchList - array of entries containing valid strings
+ pValue - value corresponding to a matching valid string
+
+Return Value:
+
+ TRUE a matching entry was found
+ FALSE otherwise
+
+--*/
+{
+ if ( SearchList != NULL) {
+
+ for ( NOTHING; SearchList->String != NULL; SearchList++) {
+
+ if ( _wcsicmp( String, SearchList->String) == 0) {
+ *pValue = SearchList->Value;
+ return( TRUE) ;
+ }
+ }
+ }
+ return( FALSE) ;
+}
+
+
+
+VOID
+Usage(
+ BOOL GoodCommand
+ )
+/*++
+
+Routine Description:
+
+ Usage of AT command.
+
+Arguments:
+
+ GoodCommand - TRUE if we have a good command input (request for help)
+ FALSE if we have a bad command input
+
+Return Value:
+
+ None.
+
+--*/
+{
+ if ( GlobalErrorReported == TRUE) {
+ PutNewLine();
+ } else if ( GoodCommand == FALSE) {
+ MessagePrint( IDS_INVALID_COMMAND );
+ }
+
+ MessagePrint( IDS_USAGE );
+}
+
+#define REG_SCHEDULE_PARMS TEXT("System\\CurrentControlSet\\Services\\Schedule\\Parameters")
+
+#define REG_SCHEDULE_USE_OLD TEXT("UseOldParsing")
+
+BOOL
+UseOldParsing()
+/*++
+
+Routine Description:
+
+ Checks the registry for
+
+ HKLM\CurrentControlSet\Services\Schedule\parameters\UseOldParsing
+
+ If present and equal to 1, then revert to 3.51 level of command line
+ parsing. Spaces in filenames will not work with this option. This is
+ intended as a migration path for customers who cannot change all their
+ command scripts that use AT.EXE right away.
+
+--*/
+{
+ BOOL fUseOld = FALSE;
+ LONG err = 0;
+
+ do { // Error breakout loop
+
+ HKEY hkeyScheduleParms;
+ DWORD dwType;
+ DWORD dwData = 0;
+ DWORD cbData = sizeof(dwData);
+
+ // Break out on any error and use the default, FALSE.
+
+ if (err = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
+ REG_SCHEDULE_PARMS,
+ 0,
+ KEY_READ,
+ &hkeyScheduleParms))
+ {
+ break;
+ }
+ if (err = RegQueryValueEx(hkeyScheduleParms,
+ REG_SCHEDULE_USE_OLD,
+ NULL,
+ &dwType,
+ (LPBYTE)&dwData,
+ &cbData ))
+ {
+ RegCloseKey( hkeyScheduleParms );
+ break;
+ }
+
+ if ( dwType == REG_DWORD && dwData == 1 )
+ {
+ fUseOld = TRUE;
+ }
+
+ RegCloseKey( hkeyScheduleParms );
+
+
+ } while (FALSE) ;
+
+ return fUseOld;
+
+}
+
+
+
+BOOL
+ValidateCommand(
+ IN int argc,
+ IN WCHAR ** argv,
+ OUT int * pCommand
+ )
+/*++
+
+Routine Description:
+
+ Examines command line to see what to do. This validates the command
+ line passed into the AT command processor. If this routine finds any
+ invalid data, the program exits with an appropriate error message.
+
+Arguments:
+
+ pCommand - pointer to command
+ argc - count of arguments
+ argv - pointer to table of arguments
+
+Return Value:
+
+ FALSE - if failure, i.e. command will not be executed
+ TRUE - if success
+
+Comment:
+
+ Parsing assumes:
+
+ non-switch (positional) parameters come first and order among these
+ parameters is important
+
+ switch parameters come second and order among these parameters is
+ NOT important
+
+ command (if present) comes last
+
+--*/
+{
+ int i; // loop index
+ int next; // index of Time or JobId argument
+ int argno; // where to start in arg string
+ BOOL DeleteFound; // did we find a delete switch
+ WCHAR * recdatap; // ptr used to build atr_command
+ DWORD recdata_len; // len of arg to put in atr_command
+ DWORD JobTime;
+ WCHAR wszTmp[MAX_COMMAND_LEN];
+ BOOL fUseOldParsing = FALSE;
+
+ if (argc == 1) {
+ *pCommand = DUMP_ALL;
+ return( TRUE);
+ }
+
+ // First look for a help switch on the command line.
+
+ for ( i = 1; i < argc; i++ ) {
+
+ if ( !_wcsicmp( argv[i], QUESTION_SW)
+ || !_wcsicmp( argv[i], QUESTION_SW_TOO)) {
+
+ *pCommand = ACTION_USAGE;
+ return( TRUE);
+ }
+ }
+
+ next = ( ArgIsServerName( argv[ 1]) == TRUE) ? 2 : 1;
+ if ( argc == next) {
+ *pCommand = DUMP_ALL;
+ return( TRUE);
+ }
+
+ if ( (ArgIsDecimalString( argv[ next], &GlobalJobId)) == TRUE) {
+
+ if ( argc == next + 1) {
+ *pCommand = DUMP_ID;
+ return( TRUE);
+ }
+
+ if ( ParseJobIdArgs( argv, argc, next + 1, &DeleteFound) == FALSE) {
+ return( FALSE); // an invalid argument
+ }
+
+ *pCommand = (DeleteFound == FALSE) ? DUMP_ID : DEL_ID;
+ return( TRUE);
+ }
+
+ //
+ // Try some variation of "AT [\\ServerName [/DELETE]"
+ //
+ if ( ParseJobIdArgs( argv, argc, next, &DeleteFound) == TRUE) {
+ *pCommand = (DeleteFound == FALSE) ? DUMP_ALL : DEL_ALL;
+ return( TRUE);
+ }
+
+ if ( ArgIsTime( argv[ next], &JobTime) == TRUE) {
+
+ *pCommand = ADD_TO_SCHEDULE;
+
+ if ( argc < next + 2) {
+ return( FALSE); // need something to do, not just time
+ }
+
+ memset( (PBYTE)&GlobalAtInfo, '\0', sizeof(GlobalAtInfo)); // initialize
+ GlobalAtInfo.Flags |= JOB_NONINTERACTIVE; // the default
+
+ if ( ParseInteractiveArg( argv[ next + 1])) {
+ next++;
+ }
+ if ( argc < next + 2) {
+ return( FALSE); // once more with feeling
+ }
+
+ if ( ParseTimeArgs( argv, argc, next + 1, &argno) == FALSE) {
+ return( FALSE);
+ }
+
+ // Copy argument strings to record.
+
+ recdatap = GlobalAtInfo.Command = GlobalAtInfoCommand;
+ recdata_len = 0;
+
+ fUseOldParsing = UseOldParsing();
+
+ for ( i = argno; i < argc; i++) {
+
+ DWORD temp;
+
+ //
+ // Fix for bug 22068 "AT command does not handle filenames with
+ // spaces." The command processor takes a quoted command line arg
+ // and puts everything between the quotes into one argv string.
+ // The quotes are stripped out. Thus, if any of the string args
+ // contain whitespace, then they must be requoted before being
+ // concatonated into the command value.
+ //
+ if (!fUseOldParsing && wcschr(argv[i], L' ') != NULL)
+ {
+ wcscpy(wszTmp, L"\"");
+ wcscat(wszTmp, argv[i]);
+ wcscat(wszTmp, L"\"");
+ }
+ else
+ {
+ wcscpy(wszTmp, argv[i]);
+ }
+
+ temp = wcslen(wszTmp) + 1;
+
+ recdata_len += temp;
+
+ if ( recdata_len > MAX_COMMAND_LEN) {
+ MessagePrint( APE_AT_COMMAND_TOO_LONG );
+ return( FALSE);
+ }
+
+ wcscpy( recdatap, wszTmp);
+ recdatap += temp;
+
+ // To construct lpszCommandLine argument to CreateProcess call
+ // we replace nuls with spaces.
+
+ *(recdatap - 1) = BLANK;
+
+ }
+
+ // Reset space back to null on last argument in string.
+
+ *(recdatap - 1) = NULLC;
+ GlobalAtInfo.JobTime = JobTime;
+ return( TRUE);
+ }
+
+ return( FALSE);
+}
+
+
+VOID
+GetTimeString(
+ DWORD Time,
+ WCHAR *Buffer,
+ int BufferLength
+ )
+/*++
+
+Routine Description:
+
+ This function converts a dword time to an ASCII string.
+
+Arguments:
+
+ Time - Time difference in dword from start of the day (i.e. 12am
+ midnight ) in milliseconds
+
+ Buffer - Pointer to the buffer to place the ASCII representation.
+
+ BufferLength - The length of buffer in bytes.
+
+Return Value:
+
+ None.
+
+--*/
+#define MINUTES_IN_HOUR 60
+#define SECONDS_IN_MINUTE 60
+{
+ WCHAR szTimeString[MAX_TIME_SIZE];
+ WCHAR *p = &szTimeString[1];
+ DWORD seconds, minutes, hours;
+ int numChars;
+ DWORD flags;
+ SYSTEMTIME st;
+
+ GetSystemTime(&st);
+ *p = NULLC;
+
+ // Check if the time format is initialized. If not, initialize it.
+ if ( GlobalTimeFormat.AMString == NULL )
+ NetpGetTimeFormat( &GlobalTimeFormat );
+
+ // Convert the time to hours, minutes, seconds
+ seconds = (Time/1000);
+ hours = seconds / (MINUTES_IN_HOUR * SECONDS_IN_MINUTE );
+ seconds -= hours * MINUTES_IN_HOUR * SECONDS_IN_MINUTE;
+ minutes = seconds / SECONDS_IN_MINUTE;
+ seconds -= minutes * SECONDS_IN_MINUTE;
+
+ st.wHour = (WORD)(hours);
+ st.wMinute = (WORD)(minutes);
+ st.wSecond = (WORD)(seconds);
+ st.wMilliseconds = 0;
+
+ flags = TIME_NOSECONDS;
+ if (!GlobalTimeFormat.TwelveHour)
+ flags |= TIME_FORCE24HOURFORMAT;
+
+ numChars = GetTimeFormatW(GetThreadLocale(),
+ flags, &st, NULL, p, MAX_TIME_SIZE-1);
+
+ if ( numChars > BufferLength )
+ numChars = BufferLength;
+
+ if (*(p+1) == ARG_SEP_CHR && GlobalTimeFormat.LeadingZero) {
+ *(--p) = TEXT('0');
+ numChars++;
+ }
+ wcsncpy( Buffer, p, numChars );
+ // Append spece for align print format. column based.
+ {
+ DWORD ColumnLength;
+
+ // character counts -> array index.
+ numChars--;
+
+ ColumnLength = GetStringColumn( Buffer );
+
+ while( ColumnLength++ < MAX_TIME_FIELD_LENGTH) {
+ Buffer[ numChars++] = BLANK;
+ }
+ Buffer[ numChars] = UNICODE_NULL;
+ }
+}
+
+
+BOOL
+InitList( PSEARCH_LIST SearchList )
+{
+ if ( SearchList != NULL) {
+
+ if ( SearchList->String != NULL ) // Already initialized
+ return TRUE;
+
+ for ( NOTHING; SearchList->MessageId != 0; SearchList++) {
+ if ( MessageGet( SearchList->MessageId,
+ &SearchList->String,
+ 0 ) == 0 )
+ {
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+
+VOID
+TermList( PSEARCH_LIST SearchList )
+{
+ if ( SearchList != NULL) {
+
+ if ( SearchList->String == NULL ) // Not initialized
+ return;
+
+ for ( NOTHING; SearchList->String != NULL; SearchList++) {
+ LocalFree( SearchList->String );
+ }
+ }
+}
+
+
+DWORD
+GetStringColumn( WCHAR *lpwstr )
+{
+ int cchNeed;
+
+ cchNeed = WideCharToMultiByte( GetConsoleOutputCP() , 0 ,
+ lpwstr , -1 ,
+ NULL , 0 ,
+ NULL , NULL );
+
+ return( (DWORD) cchNeed - 1 ); // - 1 : remove NULL
+}
diff --git a/private/net/svcdlls/at/atcmd/lmatmsg.mc b/private/net/svcdlls/at/atcmd/lmatmsg.mc
new file mode 100644
index 000000000..edd658fcf
--- /dev/null
+++ b/private/net/svcdlls/at/atcmd/lmatmsg.mc
@@ -0,0 +1,193 @@
+;/*++
+;
+;Copyright (c) 1992 Microsoft Corporation
+;
+;Module Name:
+;
+; lmatmsg.h
+;
+;Abstract:
+;
+; Definitions for AT command private errors.
+;
+;Author:
+;
+; Vladimir Z. Vulovic (vladimv) 06 - November - 1992
+;
+;Revision History:
+;
+; 06-Nov-1992 vladimv
+; Created
+; 20-Feb-1993 yihsins
+; Move hard-coded strings from atcmd.c to here.
+;
+;Notes:
+;
+; This file is generated by the MC tool from the lmatmsg.mc file.
+;
+;
+; In lanman2.1 and before, messages for AT utility used to be defined in
+; common\h\apperr2.h. In NT windows, error codes belonging to AT utility
+; have been reused by redir statistics. Therefore, we have to invent a
+; whole new message file for these lost messages!
+;
+; Note that %1 was removed from messages for APE2_AT_EACH & APE2_AT_NEXT.
+;
+;--*/
+
+MessageId=4630 SymbolicName=APE2_AT_DEL_WARNING
+Language=English
+This operation will delete all scheduled jobs.
+.
+
+MessageId=+1 SymbolicName=APE2_AT_SCHED_FILE_CLEARED
+Language=English
+The AT schedule file was cleared.
+.
+
+MessageId=+1 SymbolicName=APE2_AT_TODAY
+Language=English
+Today %0
+.
+
+MessageId=+1 SymbolicName=APE2_AT_TOMORROW
+Language=English
+Tomorrow %0
+.
+
+MessageId=+1 SymbolicName=APE2_AT_NEXT
+Language=English
+Next %0
+.
+
+MessageId=+1 SymbolicName=APE2_AT_EACH
+Language=English
+Each %0
+.
+
+;// ++++++++++++++++++++++++++++ WARNING ++++++++++++++++++++++++++
+;//
+;// The following messages up to APE2_AT_DI_INTERACTIVE are aligned to
+;// look right when printed on the screen (they appear in a table).
+;// If they are changed, make sure they occupy the same number of
+;// chars (text+space). In the case of AT_DUMP_HEADER, the column
+;// headers should start at the same place (left justified).
+;//
+;// ---------------------------------------------------------------
+
+
+MessageId=+1 SymbolicName=APE2_AT_DUMP_HEADER
+Language=English
+Status ID Day Time Command Line
+.
+
+MessageId=+1 SymbolicName=APE2_AT_DI_TASK
+Language=English
+Task ID: %0
+.
+
+MessageId=+1 SymbolicName=APE2_AT_DI_STATUS
+Language=English
+Status: %0
+.
+
+MessageId=+1 SymbolicName=APE2_AT_DI_SCHEDULE
+Language=English
+Schedule: %0
+.
+
+MessageId=+1 SymbolicName=APE2_AT_DI_TIMEOFDAY
+Language=English
+Time of day: %0
+.
+
+MessageId=+1 SymbolicName=APE2_AT_DI_COMMAND
+Language=English
+Command: %0
+.
+
+MessageId=+1 SymbolicName=APE2_AT_DI_INTERACTIVE
+Language=English
+Interactive: %0
+.
+
+MessageId=10000 SymbolicName=IDS_LOAD_LIBRARY_FAILURE
+Language=English
+LoadLibrary() fails with winError = %1!d!
+.
+MessageId=10001 SymbolicName=IDS_UNABLE_TO_MAP_TO_UNICODE
+Language=English
+Failed to convert input strings to unicode strings.
+.
+MessageId=10002 SymbolicName=IDS_UNABLE_TO_MAKE_STRING_TO_UNICODE
+Language=English
+Failed to make unicode string out of %1.
+.
+MessageId=10003 SymbolicName=IDS_ADD_NEW_JOB
+Language=English
+Added a new job with job ID = %1!d!
+.
+MessageId=10004 SymbolicName=IDS_MESSAGE_GET_ERROR
+Language=English
+FormatMessage(%1!d!) fails with winError = %2!d!
+.
+MessageId=10005 SymbolicName=IDS_MESSAGE_PRINT_ERROR
+Language=English
+FormatMessage(%1!d!) fails with winError = %2!d!
+.
+MessageId=10006 SymbolicName=IDS_INVALID_COMMAND
+Language=English
+Invalid command.%n
+.
+MessageId=10007 SymbolicName=IDS_YES
+Language=English
+YES%0
+.
+MessageId=10008 SymbolicName=IDS_DELETE
+Language=English
+DELETE%0
+.
+MessageId=10009 SymbolicName=IDS_EVERY
+Language=English
+EVERY%0
+.
+MessageId=10010 SymbolicName=IDS_NEXT
+Language=English
+NEXT%0
+.
+MessageId=10011 SymbolicName=IDS_NO
+Language=English
+NO%0
+.
+MessageId=10014 SymbolicName=IDS_USAGE
+Language=English
+The AT command schedules commands and programs to run on a computer at
+a specified time and date. The Schedule service must be running to use
+the AT command.%n
+AT [\\computername] [ [id] [/DELETE] | /DELETE [/YES]]
+AT [\\computername] time [/INTERACTIVE]
+ [ /EVERY:date[,...] | /NEXT:date[,...]] "command"%n
+\\computername Specifies a remote computer. Commands are scheduled on the
+ local computer if this parameter is omitted.
+id Is an identification number assigned to a scheduled
+ command.
+/delete Cancels a scheduled command. If id is omitted, all the
+ scheduled commands on the computer are canceled.
+/yes Used with cancel all jobs command when no further
+ confirmation is desired.
+time Specifies the time when command is to run.
+/interactive Allows the job to interact with the desktop of the user
+ who is logged on at the time the job runs.
+/every:date[,...] Runs the command on each specified day(s) of the week or
+ month. If date is omitted, the current day of the month
+ is assumed.
+/next:date[,...] Runs the specified command on the next occurrence of the
+ day (for example, next Thursday). If date is omitted, the
+ current day of the month is assumed.
+"command" Is the Windows NT command, or batch program to be run.%n
+.
+MessageId=10015 SymbolicName=IDS_INTERACTIVE
+Language=English
+INTERACTIVE%0
+.
+ \ No newline at end of file
diff --git a/private/net/svcdlls/at/atcmd/makefile b/private/net/svcdlls/at/atcmd/makefile
new file mode 100644
index 000000000..6ee4f43fa
--- /dev/null
+++ b/private/net/svcdlls/at/atcmd/makefile
@@ -0,0 +1,6 @@
+#
+# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
+# file to this component. This file merely indirects to the real make file
+# that is shared by all the components of NT OS/2
+#
+!INCLUDE $(NTMAKEENV)\makefile.def
diff --git a/private/net/svcdlls/at/atcmd/makefile.inc b/private/net/svcdlls/at/atcmd/makefile.inc
new file mode 100644
index 000000000..5157fa663
--- /dev/null
+++ b/private/net/svcdlls/at/atcmd/makefile.inc
@@ -0,0 +1,4 @@
+lmatmsg.rc: msg00001.bin
+
+lmatmsg.h msg00001.bin: lmatmsg.mc
+ mc -v lmatmsg.mc
diff --git a/private/net/svcdlls/at/atcmd/sources b/private/net/svcdlls/at/atcmd/sources
new file mode 100644
index 000000000..d931aa3e6
--- /dev/null
+++ b/private/net/svcdlls/at/atcmd/sources
@@ -0,0 +1,32 @@
+MAJORCOMP = bozo
+MINORCOMP = bozo
+
+TARGETNAME=at
+
+TARGETPATH=obj
+TARGETTYPE=PROGRAM
+
+INCLUDES=..;..\..\..\inc;..\..\..\..\inc
+
+C_DEFINES=-DRPC_NO_WINDOWS_H -DUNICODE -D_UNICODE
+
+USE_CRTDLL=1
+
+WARNING_LEVEL=-W4
+
+SOURCES= \
+ atcmd.c \
+ at.rc
+
+UMTYPE=console
+
+TARGETLIBS= \
+ $(BASEDIR)\public\sdk\lib\*\ntdll.lib \
+ $(BASEDIR)\Public\Sdk\Lib\*\netlib.lib \
+ $(BASEDIR)\public\sdk\lib\*\netapi32.lib \
+ $(BASEDIR)\Public\Sdk\Lib\*\shell32.lib \
+ $(BASEDIR)\public\sdk\lib\*\user32.lib
+
+NTTARGETFILE0= \
+ lmatmsg.h \
+ lmatmsg.mc