Leaked source code of windows server 2003
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.

2310 lines
57 KiB

  1. /*++
  2. Copyright (c) 1987-1992 Microsoft Corporation
  3. Module Name:
  4. atcmd.c
  5. Abstract:
  6. Code for AT command, to be used with SCHEDULE service on Windows NT.
  7. The module was taken from LanManager\at.c and then modified considerably
  8. to work with NT Schedule service.
  9. Author:
  10. Vladimir Z. Vulovic (vladimv) 06 - November - 1992
  11. Environment:
  12. User Mode - Win32
  13. Revision History:
  14. 06-Nov-1992 vladimv
  15. Created
  16. 20-Feb-1993 yihsins
  17. Get rid of hard coded strings and parse/print time according
  18. to user profile
  19. 25-May-1993 RonaldM
  20. Convert strings to OEM before printing to the console, since
  21. the console doesn't yet support unicode.
  22. 28-Jun-1993 RonaldM
  23. Added the "confirm" yes and no strings, which are meant to be
  24. localised. The original yes and no strings cannot be localised,
  25. because this would create batch file incompatibilities.
  26. 07-Jul-1994 vladimv
  27. Added support for interactive switch. Replaced "confirm" strings
  28. with APE2_GEN_* strings - to eliminate redundancy. The rule is
  29. that switches are not internationalizable while switch values are.
  30. --*/
  31. #include <nt.h> // DbgPrint prototype
  32. #include <ntrtl.h> // DbgPrint
  33. #include <nturtl.h> // Needed by winbase.h
  34. #include <windows.h>
  35. #include <strsafe.h>
  36. #include <winnls.h>
  37. #include <winnlsp.h> // SetThreadUILanguage
  38. #include <shellapi.h>
  39. #include <lmcons.h> // NET_API_STATUS
  40. #include <lmerr.h> // NetError codes
  41. #include <icanon.h> // NetpNameValidate
  42. #include "lmatmsg.h" // private AT error codes & messages
  43. #include <lmat.h> // AT_INFO
  44. #include <stdlib.h> // exit()
  45. #include <stdio.h> // printf
  46. #include <wchar.h> // wcslen
  47. #include <apperr.h> // APE_AT_USAGE
  48. #include <apperr2.h> // APE2_GEN_MONDAY + APE2_*
  49. #include <lmapibuf.h> // NetApiBufferFree
  50. #include <timelib.h> // NetpGetTimeFormat
  51. #include <luidate.h> // LUI_ParseTimeSinceStartOfDay
  52. #define YES_FLAG 0
  53. #define NO_FLAG 1
  54. #define INVALID_FLAG -1
  55. #define DUMP_ALL 0
  56. #define DUMP_ID 1
  57. #define ADD_TO_SCHEDULE 2
  58. #define ADD_ONETIME 3
  59. #define DEL_ID 4
  60. #define DEL_ALL 5
  61. #define ACTION_USAGE 6
  62. #define MAX_COMMAND_LEN (MAX_PATH - 1) // == 259, based on value used in scheduler service for manipulating AT jobs
  63. #define MAX_SCHED_FIELD_LENGTH 24
  64. #define PutNewLine() GenOutput( TEXT("\n"))
  65. #define PutNewLine2() GenOutput( TEXT("\n\n"))
  66. #define MAX_MSG_BUFFER 1024
  67. WCHAR ConBuf[MAX_MSG_BUFFER];
  68. #define GenOutput(fmt) \
  69. {StringCchCopyW(ConBuf, MAX_MSG_BUFFER, fmt); \
  70. ConsolePrint(ConBuf, wcslen(ConBuf));}
  71. #define GenOutputArg(fmt, a1) \
  72. {StringCchPrintfW(ConBuf, MAX_MSG_BUFFER, fmt, a1); \
  73. ConsolePrint(ConBuf, wcslen(ConBuf));}
  74. //
  75. // Formats used by printf.
  76. //
  77. #define DUMP_FMT1 TEXT("%-7.7ws")
  78. //
  79. // DUMP_FMT2 is chosen so that the most common case (id numbers less than 100)
  80. // looks good: two spaces for a number, three spaces for blanks.
  81. // Larger numbers just like in LM21 will result to shifted display.
  82. //
  83. #define DUMP_FMT2 TEXT("%2d ")
  84. #define MAX_TIME_FIELD_LENGTH 14
  85. #define DUMP_FMT3 TEXT("%ws") // for printing JobTime
  86. #define NULLC L'\0'
  87. #define BLANK L' '
  88. #define SLASH L'/'
  89. #define BACKSLASH L'\\'
  90. #define ELLIPSIS L"..."
  91. #define QUESTION_SW L"/?"
  92. #define QUESTION_SW_TOO L"-?"
  93. #define SCHED_TOK_DELIM L"," // string of valid delimiters for days & dates
  94. #define ARG_SEP_CHR L':'
  95. typedef struct _SEARCH_LIST {
  96. WCHAR * String;
  97. DWORD MessageId;
  98. DWORD Value;
  99. } SEARCH_LIST, *PSEARCH_LIST, *LPSEARCH_LIST;
  100. //
  101. // All of the values below must be bitmasks. MatchString() depends on that!
  102. //
  103. #define AT_YES_VALUE 0x0001
  104. #define AT_DELETE_VALUE 0x0002
  105. #define AT_EVERY_VALUE 0x0004
  106. #define AT_NEXT_VALUE 0x0008
  107. #define AT_NO_VALUE 0x0010
  108. #define AT_CONFIRM_YES_VALUE 0x0020
  109. #define AT_CONFIRM_NO_VALUE 0x0040
  110. #define AT_INTERACTIVE 0x0080
  111. SEARCH_LIST GlobalListTable[] = {
  112. { NULL, IDS_YES, AT_YES_VALUE},
  113. { NULL, IDS_DELETE, AT_DELETE_VALUE},
  114. { NULL, IDS_EVERY, AT_EVERY_VALUE},
  115. { NULL, IDS_NEXT, AT_NEXT_VALUE},
  116. { NULL, IDS_NO, AT_NO_VALUE},
  117. { NULL, APE2_GEN_YES, AT_CONFIRM_YES_VALUE},
  118. { NULL, APE2_GEN_NO, AT_CONFIRM_NO_VALUE},
  119. { NULL, IDS_INTERACTIVE, AT_INTERACTIVE},
  120. { NULL, 0, 0 }
  121. };
  122. SEARCH_LIST DaysOfWeekSearchList[] = {
  123. { NULL, APE2_GEN_MONDAY_ABBREV, 0},
  124. { NULL, APE2_GEN_TUESDAY_ABBREV, 1},
  125. { NULL, APE2_GEN_WEDNSDAY_ABBREV, 2},
  126. { NULL, APE2_GEN_THURSDAY_ABBREV, 3},
  127. { NULL, APE2_GEN_FRIDAY_ABBREV, 4},
  128. { NULL, APE2_GEN_SATURDAY_ABBREV, 5},
  129. { NULL, APE2_TIME_SATURDAY_ABBREV2, 5},
  130. { NULL, APE2_GEN_SUNDAY_ABBREV, 6},
  131. { NULL, APE2_GEN_MONDAY, 0},
  132. { NULL, APE2_GEN_TUESDAY, 1},
  133. { NULL, APE2_GEN_WEDNSDAY, 2},
  134. { NULL, APE2_GEN_THURSDAY, 3},
  135. { NULL, APE2_GEN_FRIDAY, 4},
  136. { NULL, APE2_GEN_SATURDAY, 5},
  137. { NULL, APE2_GEN_SUNDAY, 6},
  138. { NULL, APE2_GEN_NONLOCALIZED_MONDAY_ABBREV, 0},
  139. { NULL, APE2_GEN_NONLOCALIZED_TUESDAY_ABBREV, 1},
  140. { NULL, APE2_GEN_NONLOCALIZED_WEDNSDAY_ABBREV, 2},
  141. { NULL, APE2_GEN_NONLOCALIZED_THURSDAY_ABBREV, 3},
  142. { NULL, APE2_GEN_NONLOCALIZED_FRIDAY_ABBREV, 4},
  143. { NULL, APE2_GEN_NONLOCALIZED_SATURDAY_ABBREV, 5},
  144. { NULL, APE2_GEN_NONLOCALIZED_SATURDAY_ABBREV2, 5},
  145. { NULL, APE2_GEN_NONLOCALIZED_SUNDAY_ABBREV, 6},
  146. { NULL, APE2_GEN_NONLOCALIZED_MONDAY, 0},
  147. { NULL, APE2_GEN_NONLOCALIZED_TUESDAY, 1},
  148. { NULL, APE2_GEN_NONLOCALIZED_WEDNSDAY, 2},
  149. { NULL, APE2_GEN_NONLOCALIZED_THURSDAY, 3},
  150. { NULL, APE2_GEN_NONLOCALIZED_FRIDAY, 4},
  151. { NULL, APE2_GEN_NONLOCALIZED_SATURDAY, 5},
  152. { NULL, APE2_GEN_NONLOCALIZED_SUNDAY, 6},
  153. { NULL, 0, 0 }
  154. };
  155. BOOL
  156. AreYouSure(
  157. VOID
  158. );
  159. BOOL
  160. ArgIsServerName(
  161. WCHAR * string
  162. );
  163. BOOL
  164. ArgIsTime(
  165. IN WCHAR * timestr,
  166. OUT DWORD_PTR *pJobTime
  167. );
  168. BOOL
  169. ArgIsDecimalString(
  170. IN WCHAR * pDecimalString,
  171. OUT PDWORD pNumber
  172. );
  173. DWORD
  174. ConsolePrint(
  175. IN LPWSTR pch,
  176. IN int cch
  177. );
  178. int
  179. FileIsConsole(
  180. int fh
  181. );
  182. BOOL
  183. IsDayOfMonth(
  184. IN WCHAR * pToken,
  185. OUT PDWORD pDay
  186. );
  187. BOOL
  188. IsDayOfWeek(
  189. IN WCHAR * pToken,
  190. OUT PDWORD pDay
  191. );
  192. NET_API_STATUS
  193. JobAdd(
  194. VOID
  195. );
  196. NET_API_STATUS
  197. JobEnum(
  198. VOID
  199. );
  200. NET_API_STATUS
  201. JobGetInfo(
  202. VOID
  203. );
  204. DWORD
  205. MatchString(
  206. WCHAR * name,
  207. DWORD mask
  208. );
  209. DWORD
  210. MessageGet(
  211. IN DWORD MessageId,
  212. IN LPWSTR *buffer,
  213. IN DWORD Size
  214. );
  215. DWORD
  216. MessagePrint(
  217. IN DWORD MessageId,
  218. ...
  219. );
  220. BOOL
  221. ParseJobIdArgs(
  222. WCHAR ** argv,
  223. int argc,
  224. int argno,
  225. PBOOL pDeleteFound
  226. );
  227. BOOL
  228. ParseTimeArgs(
  229. WCHAR ** argv,
  230. int argc,
  231. int argno,
  232. int * pargno
  233. );
  234. VOID
  235. PrintDay(
  236. int type,
  237. DWORD DaysOfMonth,
  238. UCHAR DaysOfWeek,
  239. UCHAR Flags
  240. );
  241. VOID
  242. PrintLine(
  243. VOID
  244. );
  245. VOID
  246. PrintTime(
  247. DWORD_PTR JobTime
  248. );
  249. BOOL
  250. TraverseSearchList(
  251. PWCHAR String,
  252. PSEARCH_LIST SearchList,
  253. PDWORD pValue
  254. );
  255. VOID
  256. Usage(
  257. BOOL GoodCommand
  258. );
  259. BOOL
  260. ValidateCommand(
  261. IN int argc,
  262. IN WCHAR ** argv,
  263. OUT int * pCommand
  264. );
  265. VOID
  266. GetTimeString(
  267. DWORD_PTR Time,
  268. WCHAR *Buffer,
  269. int BufferLength
  270. );
  271. BOOL
  272. InitList(
  273. PSEARCH_LIST SearchList
  274. );
  275. VOID
  276. TermList(
  277. PSEARCH_LIST SearchList
  278. );
  279. DWORD
  280. GetStringColumn(
  281. WCHAR *
  282. );
  283. AT_INFO GlobalAtInfo; // buffer for scheduling new jobs
  284. WCHAR GlobalAtInfoCommand[ MAX_COMMAND_LEN + 1];
  285. DWORD GlobalJobId; // id of job in question
  286. PWSTR GlobalServerName;
  287. HANDLE GlobalMessageHandle;
  288. BOOL GlobalYes;
  289. BOOL GlobalDeleteAll;
  290. BOOL GlobalErrorReported;
  291. BOOL bDBCS;
  292. CHAR ** GlobalCharArgv; // keeps original input
  293. NET_TIME_FORMAT GlobalTimeFormat = {0};
  294. // In OS/2 it used to be OK to call "exit()" with a negative number. In
  295. // NT however, "exit()" should be called with a positive number only (a
  296. // valid windows error code?!). Note that OS/2 AT command used to call
  297. // exit(+1) for bad user input, and exit(-1) where -1 would get mapped to
  298. // 255 for other errors. To keep things simple and to avoid calling exit()
  299. // with a negative number, NT AT command calls exit(+1) for all possible
  300. // errors.
  301. #define AT_GENERIC_ERROR 1
  302. VOID __cdecl
  303. main(
  304. int argc,
  305. CHAR ** charArgv
  306. )
  307. /*++
  308. Routine Description:
  309. Main module. Note that strings (for now) arrive as asciiz even
  310. if you compile app for UNICODE.
  311. Arguments:
  312. argc - argument count
  313. charArgv - array of ascii strings
  314. Return Value:
  315. None.
  316. --*/
  317. {
  318. NET_API_STATUS status = NERR_Success;
  319. int command; // what to do
  320. WCHAR ** argv;
  321. DWORD cp;
  322. CPINFO CurrentCPInfo;
  323. WCHAR dllPath[MAX_PATH +2];
  324. GlobalYes = FALSE;
  325. GlobalDeleteAll = FALSE;
  326. GlobalErrorReported = FALSE;
  327. GlobalCharArgv = charArgv;
  328. /*
  329. Added for bilingual message support. This is needed for FormatMessage
  330. to work correctly. (Called from DosGetMessage).
  331. Get current CodePage Info. We need this to decide whether
  332. or not to use half-width characters.
  333. This code has been updated to use the new SetThreadUILanguage() rather
  334. than the older SetThreadLocale().
  335. */
  336. GetCPInfo(cp=GetConsoleOutputCP(), &CurrentCPInfo);
  337. switch ( cp ) {
  338. case 932:
  339. case 936:
  340. case 949:
  341. case 950:
  342. bDBCS = TRUE;
  343. break;
  344. default:
  345. bDBCS = FALSE;
  346. break;
  347. }
  348. SetThreadUILanguage(0);
  349. if (!ExpandEnvironmentStringsW(L"%systemroot%\\system32\\netmsg.dll", dllPath, MAX_PATH +1))
  350. {
  351. MessagePrint( IDS_LOAD_LIBRARY_FAILURE, GetLastError());
  352. exit( AT_GENERIC_ERROR);
  353. }
  354. GlobalMessageHandle = LoadLibrary(dllPath);
  355. if ( GlobalMessageHandle == NULL) {
  356. MessagePrint( IDS_LOAD_LIBRARY_FAILURE, GetLastError());
  357. exit( AT_GENERIC_ERROR);
  358. }
  359. if ( ( argv = CommandLineToArgvW( GetCommandLineW(), &argc)) == NULL) {
  360. MessagePrint( IDS_UNABLE_TO_MAP_TO_UNICODE );
  361. exit( AT_GENERIC_ERROR);
  362. }
  363. if ( ValidateCommand( argc, argv, &command) == FALSE) {
  364. Usage( FALSE);
  365. exit( AT_GENERIC_ERROR);
  366. }
  367. switch( command) {
  368. case DUMP_ALL:
  369. status = JobEnum();
  370. break;
  371. case DUMP_ID:
  372. status = JobGetInfo();
  373. break;
  374. case ADD_TO_SCHEDULE:
  375. status = JobAdd();
  376. break;
  377. case DEL_ALL:
  378. if ( AreYouSure() == FALSE) {
  379. break;
  380. }
  381. status = NetScheduleJobDel(
  382. GlobalServerName,
  383. 0,
  384. (DWORD)-1
  385. );
  386. if ( status == NERR_Success || status == APE_AT_ID_NOT_FOUND) {
  387. break;
  388. }
  389. MessagePrint( status );
  390. break;
  391. case DEL_ID:
  392. status = NetScheduleJobDel(
  393. GlobalServerName,
  394. GlobalJobId,
  395. GlobalJobId
  396. );
  397. if ( status == NERR_Success) {
  398. break;
  399. }
  400. MessagePrint( status );
  401. break;
  402. case ACTION_USAGE:
  403. Usage( TRUE);
  404. status = NERR_Success;
  405. break;
  406. }
  407. TermList( GlobalListTable);
  408. TermList( DaysOfWeekSearchList);
  409. LocalFree( GlobalTimeFormat.AMString );
  410. LocalFree( GlobalTimeFormat.PMString );
  411. LocalFree( GlobalTimeFormat.DateFormat );
  412. LocalFree( GlobalTimeFormat.TimeSeparator );
  413. exit( status == NERR_Success ? ERROR_SUCCESS : AT_GENERIC_ERROR);
  414. }
  415. BOOL
  416. AreYouSure(
  417. VOID
  418. )
  419. /*++
  420. Routine Description:
  421. Make sure user really wants to delete all jobs.
  422. Arguments:
  423. None.
  424. Return Value:
  425. TRUE if user really wants to go ahead.
  426. FALSE otherwise.
  427. --*/
  428. {
  429. register int retries = 0;
  430. WCHAR rbuf[ 17];
  431. WCHAR * smallBuffer = NULL;
  432. DWORD Value;
  433. int cch;
  434. int retc;
  435. if ( GlobalYes == TRUE) {
  436. return( TRUE);
  437. }
  438. if ( MessagePrint( APE2_AT_DEL_WARNING ) == 0) {
  439. exit( AT_GENERIC_ERROR);
  440. }
  441. for ( ; ;) {
  442. if ( MessageGet(
  443. APE2_GEN_DEFAULT_NO, // MessageId
  444. &smallBuffer, // lpBuffer
  445. 0
  446. ) == 0) {
  447. exit( AT_GENERIC_ERROR);
  448. }
  449. if ( MessagePrint( APE_OkToProceed, smallBuffer) == 0) {
  450. exit( AT_GENERIC_ERROR);
  451. }
  452. LocalFree( smallBuffer );
  453. if (FileIsConsole(STD_INPUT_HANDLE)) {
  454. retc = ReadConsole(GetStdHandle(STD_INPUT_HANDLE),rbuf,16,&cch,0);
  455. if (retc) {
  456. //
  457. // Get rid of cr/lf
  458. //
  459. if (wcschr(rbuf, TEXT('\r')) == NULL) {
  460. if (wcschr(rbuf, TEXT('\n')))
  461. *wcschr(rbuf, TEXT('\n')) = NULLC;
  462. }
  463. else
  464. *wcschr(rbuf, TEXT('\r')) = NULLC;
  465. }
  466. }
  467. else {
  468. CHAR oemBuf[ 17 ];
  469. ZeroMemory(oemBuf, 17);
  470. retc = (fgets(oemBuf, 16, stdin) != 0);
  471. #if DBG
  472. fprintf(stderr, "got >%s<\n", oemBuf);
  473. #endif
  474. cch = 0;
  475. if (retc) {
  476. if (strchr(oemBuf, '\n')) {
  477. *strchr(oemBuf, '\n') = '\0';
  478. }
  479. cch = MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED,
  480. oemBuf, strlen(oemBuf)+1, rbuf, 16);
  481. }
  482. }
  483. #if DBG
  484. fprintf(stderr, "cch = %d, retc = %d\n", cch, retc);
  485. #endif
  486. if (!retc || cch == 0)
  487. return( FALSE);
  488. #if DBG
  489. fprintf(stderr, "converted to >%ws<\n", rbuf);
  490. #endif
  491. Value = MatchString(_wcsupr(rbuf), AT_CONFIRM_NO_VALUE | AT_CONFIRM_YES_VALUE);
  492. if ( Value == AT_CONFIRM_NO_VALUE) {
  493. return( FALSE);
  494. } else if ( Value == AT_CONFIRM_YES_VALUE) {
  495. break;
  496. }
  497. if ( ++retries >= 3) {
  498. MessagePrint( APE_NoGoodResponse );
  499. return( FALSE);
  500. }
  501. if ( MessagePrint( APE_UtilInvalidResponse ) == 0) {
  502. exit( AT_GENERIC_ERROR);
  503. }
  504. }
  505. return( TRUE);
  506. }
  507. BOOL
  508. ArgIsServerName(
  509. WCHAR * string
  510. )
  511. /*++
  512. Routine Description:
  513. Checks if string is a server name. Validation is really primitive, eg
  514. strings like "\\\threeslashes" pass the test.
  515. Arguments:
  516. string - pointer to string that may represent a server name
  517. Return Value:
  518. TRUE - string is (or might be) a valid server name
  519. FALSE - string is not a valid server name
  520. --*/
  521. {
  522. NET_API_STATUS ApiStatus;
  523. if (string[0] == BACKSLASH && string[1] == BACKSLASH && string[2] != 0) {
  524. ApiStatus = NetpNameValidate(
  525. NULL, // no server name.
  526. &string[2], // name to validate
  527. NAMETYPE_COMPUTER,
  528. LM2X_COMPATIBLE); // flags
  529. if (ApiStatus != NO_ERROR) {
  530. return (FALSE);
  531. }
  532. GlobalServerName = string;
  533. return( TRUE);
  534. }
  535. return( FALSE); // GlobalServerName is NULL at load time
  536. }
  537. BOOL
  538. ArgIsTime(
  539. IN WCHAR * timestr,
  540. OUT DWORD_PTR *pJobTime
  541. )
  542. /*++
  543. Routine Description:
  544. Determines whether string is a time or not. Validates that string
  545. passed into it is in the form of HH:MM. It searches the string for
  546. a ":" and then validates that the preceeding data is numeric & in a
  547. valid range for hours. It then validates the string after the ":"
  548. is numeric & in a validate range for minutes. If all the tests are
  549. passed the TRUE is returned.
  550. Arguments:
  551. timestr - string to check whether it is a time
  552. JobTime - ptr to number of miliseconds
  553. Return Value:
  554. TRUE - timestr was a time in HH:MM format
  555. FALSE - timestr wasn't at time
  556. --*/
  557. {
  558. CHAR buffer[MAX_TIME_SIZE];
  559. USHORT ParseLen;
  560. BOOL fDummy;
  561. if ( timestr == NULL )
  562. return FALSE;
  563. if ( !WideCharToMultiByte( CP_ACP,
  564. 0,
  565. timestr,
  566. -1,
  567. buffer,
  568. sizeof( buffer )/sizeof(CHAR),
  569. NULL,
  570. &fDummy ))
  571. {
  572. return FALSE;
  573. }
  574. if ( LUI_ParseTimeSinceStartOfDay( buffer, pJobTime, &ParseLen, 0) )
  575. return FALSE;
  576. // LUI_ParseTimeSinceStartOfDay returns the time in seconds.
  577. // Hence, we need to convert it to microseconds.
  578. *pJobTime *= 1000;
  579. return( TRUE);
  580. }
  581. BOOL
  582. ArgIsDecimalString(
  583. IN WCHAR * pDecimalString,
  584. OUT PDWORD pNumber
  585. )
  586. /*++
  587. Routine Description:
  588. This routine converts a string into a DWORD if it possibly can.
  589. The conversion is successful if string is decimal numeric and
  590. does not lead to an overflow.
  591. Arguments:
  592. pDecimalString ptr to decimal string
  593. pNumber ptr to number
  594. Return Value:
  595. FALSE invalid number
  596. TRUE valid number
  597. --*/
  598. {
  599. DWORD Value;
  600. DWORD OldValue;
  601. DWORD digit;
  602. if ( pDecimalString == NULL || *pDecimalString == 0) {
  603. return( FALSE);
  604. }
  605. Value = 0;
  606. while ( (digit = *pDecimalString++) != 0) {
  607. if ( digit < L'0' || digit > L'9') {
  608. return( FALSE); // not a decimal string
  609. }
  610. OldValue = Value;
  611. Value = digit - L'0' + 10 * Value;
  612. if ( Value < OldValue) {
  613. return( FALSE); // overflow
  614. }
  615. }
  616. *pNumber = Value;
  617. return( TRUE);
  618. }
  619. BOOL
  620. IsDayOfMonth(
  621. IN WCHAR * pToken,
  622. OUT PDWORD pDay
  623. )
  624. /*++
  625. Routine Description:
  626. Converts a string into a number for the day of the month, if it can
  627. possibly do so. Note that "first" == 1, ...
  628. Arguments:
  629. pToken pointer to schedule token for the day of the month
  630. pDay pointer to index of day in a month
  631. Return Value:
  632. TRUE if a valid schedule token
  633. FALSE otherwise
  634. --*/
  635. {
  636. return ( ArgIsDecimalString( pToken, pDay) == TRUE && *pDay >= 1
  637. && *pDay <= 31);
  638. }
  639. BOOL
  640. IsDayOfWeek(
  641. WCHAR * pToken,
  642. PDWORD pDay
  643. )
  644. /*++
  645. Routine Description:
  646. This routine converts a string day of the week into a integer
  647. offset into the week if it possibly can. Note that Monday==0,
  648. ..., Sunday == 6.
  649. Arguments:
  650. pToken pointer to schedule token for the day of a week
  651. pDay pointer to index of day in a month
  652. Return Value:
  653. TRUE if a valid schedule token
  654. FALSE otherwise
  655. --*/
  656. {
  657. if ( !InitList( DaysOfWeekSearchList ) )
  658. {
  659. // Error already reported
  660. exit( -1 );
  661. }
  662. return( TraverseSearchList(
  663. pToken,
  664. DaysOfWeekSearchList,
  665. pDay
  666. ));
  667. }
  668. NET_API_STATUS
  669. JobAdd(
  670. VOID
  671. )
  672. /*++
  673. Routine Description:
  674. Adds a new item to schedule.
  675. Arguments:
  676. None. Uses globals.
  677. Return Value:
  678. NET_API_STATUS return value of remote api call
  679. --*/
  680. {
  681. NET_API_STATUS status;
  682. for ( ; ; ) {
  683. status = NetScheduleJobAdd(
  684. GlobalServerName,
  685. (LPBYTE)&GlobalAtInfo,
  686. &GlobalJobId
  687. );
  688. if ( status == ERROR_INVALID_PARAMETER &&
  689. GlobalAtInfo.Flags & JOB_NONINTERACTIVE) {
  690. //
  691. // We may have failed because we are talking to a down level
  692. // server that does not know about JOB_NONINTERACTIVE bit.
  693. // Clear the bit, and try again.
  694. // A better approach would be to check the version of the
  695. // server before making NetScheduleJobAdd() call, adjust the
  696. // bit appropriately and only then call NetScheduleJobAdd().
  697. //
  698. GlobalAtInfo.Flags &= ~JOB_NONINTERACTIVE;
  699. } else {
  700. break;
  701. }
  702. }
  703. if ( status == NERR_Success) {
  704. MessagePrint( IDS_ADD_NEW_JOB, GlobalJobId );
  705. } else {
  706. if ( MessagePrint( status ) == 0) {
  707. exit( AT_GENERIC_ERROR);
  708. }
  709. }
  710. return( status);
  711. }
  712. NET_API_STATUS
  713. JobEnum(
  714. VOID
  715. )
  716. /*++
  717. Routine Description:
  718. This does all of the processing necessary to dump out the entire
  719. schedule file. It loops through on each record and formats its
  720. information for printing and then goes to the next.
  721. Arguments:
  722. None. Uses globals.
  723. Return Value:
  724. ERROR_SUCCESS if everything enumerated OK
  725. error returned by remote api otherwise
  726. --*/
  727. {
  728. BOOL first = TRUE;
  729. DWORD ResumeJobId = 0;
  730. NET_API_STATUS status = NERR_Success;
  731. PAT_ENUM pAtEnum;
  732. DWORD EntriesRead;
  733. DWORD TotalEntries;
  734. LPVOID EnumBuffer;
  735. DWORD length;
  736. WCHAR * smallBuffer = NULL;
  737. for ( ; ;) {
  738. status = NetScheduleJobEnum(
  739. GlobalServerName,
  740. (LPBYTE *)&EnumBuffer,
  741. (DWORD)-1,
  742. &EntriesRead,
  743. &TotalEntries,
  744. &ResumeJobId
  745. );
  746. if ( status != ERROR_SUCCESS && status != ERROR_MORE_DATA) {
  747. length = MessagePrint( status );
  748. if ( length == 0) {
  749. exit( AT_GENERIC_ERROR);
  750. }
  751. return( status);
  752. }
  753. ASSERT( status == ERROR_SUCCESS ? TotalEntries == EntriesRead
  754. : TotalEntries > EntriesRead);
  755. if ( TotalEntries == 0) {
  756. break; // no items found
  757. }
  758. if ( first == TRUE) {
  759. length = MessagePrint( APE2_AT_DUMP_HEADER );
  760. if ( length == 0) {
  761. exit( AT_GENERIC_ERROR);
  762. }
  763. PrintLine(); // line across screen
  764. first = FALSE;
  765. }
  766. for ( pAtEnum = EnumBuffer; EntriesRead-- > 0; pAtEnum++) {
  767. if ( pAtEnum->Flags & JOB_EXEC_ERROR) {
  768. if ( MessageGet( APE2_GEN_ERROR, &smallBuffer, 0 ) == 0) {
  769. // error reported already
  770. exit( AT_GENERIC_ERROR);
  771. }
  772. GenOutputArg( DUMP_FMT1, smallBuffer );
  773. LocalFree( smallBuffer );
  774. } else {
  775. GenOutputArg( DUMP_FMT1, L"");
  776. }
  777. GenOutputArg( DUMP_FMT2, pAtEnum->JobId);
  778. PrintDay( DUMP_ALL, pAtEnum->DaysOfMonth, pAtEnum->DaysOfWeek,
  779. pAtEnum->Flags);
  780. PrintTime( pAtEnum->JobTime);
  781. GenOutputArg( TEXT("%ws\n"), pAtEnum->Command);
  782. }
  783. if ( EnumBuffer != NULL) {
  784. (VOID)NetApiBufferFree( (LPVOID)EnumBuffer);
  785. EnumBuffer = NULL;
  786. }
  787. if ( status == ERROR_SUCCESS) {
  788. break; // we have read & displayed all the items
  789. }
  790. }
  791. if ( first == TRUE) {
  792. MessagePrint( APE_EmptyList );
  793. }
  794. return( ERROR_SUCCESS);
  795. }
  796. NET_API_STATUS
  797. JobGetInfo(
  798. VOID
  799. )
  800. /*++
  801. Routine Description:
  802. This prints out the schedule of an individual items schedule.
  803. Arguments:
  804. None. Uses globals.
  805. Return Value:
  806. NET_API_STATUS value returned by remote api
  807. --*/
  808. {
  809. PAT_INFO pAtInfo = NULL;
  810. NET_API_STATUS status;
  811. status = NetScheduleJobGetInfo(
  812. GlobalServerName,
  813. GlobalJobId,
  814. (LPBYTE *)&pAtInfo
  815. );
  816. if ( status != NERR_Success) {
  817. MessagePrint( status );
  818. return( status);
  819. }
  820. PutNewLine();
  821. MessagePrint( APE2_AT_DI_TASK );
  822. GenOutputArg( TEXT("%d"), GlobalJobId);
  823. PutNewLine();
  824. MessagePrint( APE2_AT_DI_STATUS );
  825. MessagePrint( (pAtInfo->Flags & JOB_EXEC_ERROR) != 0 ?
  826. APE2_GEN_ERROR : APE2_GEN_OK );
  827. PutNewLine();
  828. MessagePrint( APE2_AT_DI_SCHEDULE );
  829. PrintDay( DUMP_ID, pAtInfo->DaysOfMonth, pAtInfo->DaysOfWeek,
  830. pAtInfo->Flags);
  831. PutNewLine();
  832. MessagePrint( APE2_AT_DI_TIMEOFDAY );
  833. PrintTime( pAtInfo->JobTime);
  834. PutNewLine();
  835. MessagePrint( APE2_AT_DI_INTERACTIVE);
  836. MessagePrint( (pAtInfo->Flags & JOB_NONINTERACTIVE) == 0 ?
  837. APE2_GEN_YES : APE2_GEN_NO );
  838. PutNewLine();
  839. MessagePrint( APE2_AT_DI_COMMAND );
  840. GenOutputArg( TEXT("%ws\n"), pAtInfo->Command);
  841. PutNewLine2();
  842. (VOID)NetApiBufferFree( (LPVOID)pAtInfo);
  843. return( NERR_Success);
  844. }
  845. DWORD
  846. MatchString(
  847. WCHAR * name,
  848. DWORD Values
  849. )
  850. /*++
  851. Routine Description:
  852. Parses switch string and returns NULL for an invalid switch,
  853. and -1 for an ambiguous switch.
  854. Arguments:
  855. name - pointer to string we need to examine
  856. Values - bitmask of values of interest
  857. Return Value:
  858. Pointer to command, or NULL or -1.
  859. --*/
  860. {
  861. WCHAR * String;
  862. PSEARCH_LIST pCurrentList;
  863. WCHAR * CurrentString;
  864. DWORD FoundValue;
  865. int nmatches;
  866. int longest;
  867. if ( !InitList( GlobalListTable ) )
  868. {
  869. // Error already reported
  870. exit( -1 );
  871. }
  872. for ( pCurrentList = GlobalListTable,
  873. longest = nmatches = 0,
  874. FoundValue = 0;
  875. (CurrentString = pCurrentList->String) != NULL;
  876. pCurrentList++) {
  877. if ( (Values & pCurrentList->Value) == 0) {
  878. continue; // skip this List
  879. }
  880. for ( String = name; *String == *CurrentString++; String++) {
  881. if ( *String == 0) {
  882. return( pCurrentList->Value); // exact match
  883. }
  884. }
  885. if ( !*String) {
  886. if ( String - name > longest) {
  887. longest = (int)(String - name);
  888. nmatches = 1;
  889. FoundValue = pCurrentList->Value;
  890. } else if ( String - name == longest) {
  891. nmatches++;
  892. }
  893. }
  894. }
  895. // 0 corresponds to no match at all (invalid List)
  896. // while -1 corresponds to multiple match (ambiguous List).
  897. if ( nmatches != 1) {
  898. return ( (nmatches == 0) ? 0 : -1);
  899. }
  900. return( FoundValue);
  901. }
  902. DWORD
  903. MessageGet(
  904. IN DWORD MessageId,
  905. OUT LPWSTR *buffer,
  906. IN DWORD Size
  907. )
  908. /*++
  909. Routine Description:
  910. Fills in the unicode message corresponding to a given message id,
  911. provided that a message can be found and that it fits in a supplied
  912. buffer.
  913. Arguments:
  914. MessageId - message id
  915. buffer - pointer to caller supplied buffer
  916. Size - size (always in bytes) of supplied buffer,
  917. If size is 0, buffer will be allocated by FormatMessage.
  918. Return Value:
  919. Count of characters, not counting the terminating null character,
  920. returned in the buffer. Zero return value indicates failure.
  921. --*/
  922. {
  923. DWORD length;
  924. LPVOID lpSource;
  925. DWORD dwFlags;
  926. if ( MessageId < NERR_BASE) {
  927. //
  928. // Get message from system.
  929. //
  930. lpSource = NULL; // redundant step according to FormatMessage() spec
  931. dwFlags = FORMAT_MESSAGE_FROM_SYSTEM;
  932. } else if ( ( MessageId >= APE2_AT_DEL_WARNING
  933. && MessageId <= APE2_AT_DI_INTERACTIVE)
  934. || ( MessageId >= IDS_LOAD_LIBRARY_FAILURE
  935. && MessageId <= IDS_INTERACTIVE )) {
  936. //
  937. // Get message from this module.
  938. //
  939. lpSource = NULL;
  940. dwFlags = FORMAT_MESSAGE_FROM_HMODULE;
  941. } else {
  942. //
  943. // Get message from netmsg.dll.
  944. //
  945. lpSource = GlobalMessageHandle;
  946. dwFlags = FORMAT_MESSAGE_FROM_HMODULE;
  947. }
  948. if ( Size == 0 )
  949. dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER;
  950. length = FormatMessage(
  951. dwFlags, // dwFlags
  952. lpSource, // lpSource
  953. MessageId, // MessageId
  954. 0, // dwLanguageId
  955. (LPWSTR) buffer, // lpBuffer
  956. Size, // nSize
  957. NULL // lpArguments
  958. );
  959. if ( length == 0) {
  960. // MessagePrint( IDS_MESSAGE_GET_ERROR, MessageId, GetLastError());
  961. }
  962. return( length);
  963. } // MessageGet()
  964. int
  965. FileIsConsole(
  966. int fh
  967. )
  968. {
  969. unsigned htype ;
  970. htype = GetFileType(GetStdHandle(fh));
  971. htype &= ~FILE_TYPE_REMOTE;
  972. if ( htype == FILE_TYPE_CHAR )
  973. {
  974. HANDLE hFile;
  975. DWORD dwMode;
  976. hFile = GetStdHandle(fh);
  977. return GetConsoleMode(hFile,&dwMode);
  978. }
  979. return FALSE;
  980. }
  981. DWORD
  982. ConsolePrint(
  983. LPWSTR pch,
  984. int cch
  985. )
  986. {
  987. int cchOut =0;
  988. int err;
  989. CHAR *pchOemBuffer;
  990. if (FileIsConsole(STD_OUTPUT_HANDLE)) {
  991. err = WriteConsole(
  992. GetStdHandle(STD_OUTPUT_HANDLE),
  993. pch, cch,
  994. &cchOut, NULL);
  995. if (!err || cchOut != cch)
  996. goto try_again;
  997. }
  998. else if ( cch != 0) {
  999. try_again:
  1000. cchOut = WideCharToMultiByte(CP_OEMCP, 0, pch, cch, NULL, 0, NULL,NULL);
  1001. if (cchOut == 0)
  1002. return 0;
  1003. if ((pchOemBuffer = (CHAR *)malloc(cchOut)) != NULL) {
  1004. WideCharToMultiByte(CP_OEMCP, 0, pch, cch,
  1005. pchOemBuffer, cchOut, NULL, NULL);
  1006. WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
  1007. pchOemBuffer, cchOut, &cch, NULL);
  1008. free(pchOemBuffer);
  1009. }
  1010. }
  1011. return cchOut;
  1012. }
  1013. DWORD
  1014. MessagePrint(
  1015. IN DWORD MessageId,
  1016. ...
  1017. )
  1018. /*++
  1019. Routine Description:
  1020. Finds the unicode message corresponding to the supplied message id,
  1021. merges it with caller supplied string(s), and prints the resulting
  1022. string.
  1023. Arguments:
  1024. MessageId - message id
  1025. Return Value:
  1026. Count of characters, not counting the terminating null character,
  1027. printed by this routine. Zero return value indicates failure.
  1028. --*/
  1029. {
  1030. va_list arglist;
  1031. WCHAR * buffer = NULL;
  1032. DWORD length;
  1033. LPVOID lpSource;
  1034. DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER;
  1035. va_start( arglist, MessageId );
  1036. if ( MessageId < NERR_BASE) {
  1037. //
  1038. // Get message from system.
  1039. //
  1040. lpSource = NULL; // redundant step according to FormatMessage() spec
  1041. dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM;
  1042. } else if ( ( MessageId >= APE2_AT_DEL_WARNING
  1043. && MessageId <= APE2_AT_DI_INTERACTIVE)
  1044. || ( MessageId >= IDS_LOAD_LIBRARY_FAILURE
  1045. && MessageId <= IDS_INTERACTIVE )) {
  1046. //
  1047. // Get message from this module.
  1048. //
  1049. lpSource = NULL;
  1050. dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
  1051. } else {
  1052. //
  1053. // Get message from netmsg.dll.
  1054. //
  1055. lpSource = GlobalMessageHandle;
  1056. dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
  1057. }
  1058. length = FormatMessage(
  1059. dwFlags, // dwFlags
  1060. lpSource, // lpSource
  1061. MessageId, // MessageId
  1062. 0L, // dwLanguageId
  1063. (LPTSTR)&buffer, // lpBuffer
  1064. 0, // size
  1065. &arglist // lpArguments
  1066. );
  1067. if(length)
  1068. length = ConsolePrint(buffer, length);
  1069. LocalFree(buffer);
  1070. return( length);
  1071. } // MessagePrint()
  1072. BOOL
  1073. ParseJobIdArgs(
  1074. WCHAR ** argv,
  1075. int argc,
  1076. int argno,
  1077. PBOOL pDeleteFound
  1078. )
  1079. /*++
  1080. Routine Description:
  1081. Parses arguments for commands containing JobId (these can be JobGetInfo
  1082. and JobDel commands). It loops through JobId arguments making sure that
  1083. we have at most one "yes-no" switch and at most one "delete" switch and
  1084. nothing else.
  1085. Arguments:
  1086. argv argument list
  1087. argc number of arguments to parse
  1088. argno index of argument to begin parsing from
  1089. pDeleteFound did we find a delete switch or not
  1090. Return Value:
  1091. FALSE invalid argument found
  1092. TRUE valid arguments
  1093. --*/
  1094. {
  1095. BOOL FoundDeleteSwitch;
  1096. for ( FoundDeleteSwitch = FALSE; argno < argc; argno++) {
  1097. WCHAR * argp;
  1098. DWORD length;
  1099. DWORD Value;
  1100. argp = argv[ argno];
  1101. if ( *argp++ != SLASH) {
  1102. return( FALSE); // not a switch
  1103. }
  1104. _wcsupr( argp);
  1105. length = wcslen( argp);
  1106. Value = MatchString( argp, AT_YES_VALUE | AT_DELETE_VALUE);
  1107. if ( Value == AT_YES_VALUE) {
  1108. if ( GlobalYes == TRUE) {
  1109. return( FALSE); // multiple instances of yes switch
  1110. }
  1111. GlobalYes = TRUE;
  1112. continue;
  1113. }
  1114. if ( Value == AT_DELETE_VALUE) {
  1115. if ( FoundDeleteSwitch == TRUE) {
  1116. return( FALSE); // duplicate delete switch
  1117. }
  1118. FoundDeleteSwitch = TRUE;
  1119. continue;
  1120. }
  1121. return( FALSE); // an unknown switch
  1122. }
  1123. *pDeleteFound = FoundDeleteSwitch;
  1124. return( TRUE);
  1125. } // ParseJobIdArgs()
  1126. BOOL
  1127. ParseTimeArgs(
  1128. WCHAR ** argv,
  1129. int argc,
  1130. int argno,
  1131. int * pargno
  1132. )
  1133. /*++
  1134. Routine Description:
  1135. Parses arguments for command addition.
  1136. Arguments:
  1137. argv argument list
  1138. argc count of args
  1139. argno index of the first arg to validate
  1140. pargno ptr to the index of the first non-switch arg
  1141. Return Value:
  1142. TRUE all arguments are valid
  1143. FALSE otherwise
  1144. --*/
  1145. {
  1146. DWORD day_no; // day number for scheduling
  1147. DWORD NextCount = 0; // count of next switches
  1148. DWORD EveryCount = 0; // count of every switches
  1149. WCHAR * argp; // ptr to arg string
  1150. WCHAR * schedp; // work ptr to arg string
  1151. DWORD Value; // bitmask
  1152. for ( NOTHING; argno < argc; argno++) {
  1153. argp = argv[ argno];
  1154. if ( *argp++ != SLASH) {
  1155. break; // found non-switch, we are done
  1156. }
  1157. schedp = wcschr( argp, ARG_SEP_CHR);
  1158. if ( schedp == NULL) {
  1159. return( FALSE);
  1160. }
  1161. _wcsupr( argp); // upper case entire input, not just the switch name
  1162. *schedp = 0;
  1163. Value = MatchString( argp, AT_NEXT_VALUE | AT_EVERY_VALUE);
  1164. if ( Value == AT_NEXT_VALUE) {
  1165. NextCount++;
  1166. } else if ( Value == AT_EVERY_VALUE) {
  1167. EveryCount++;
  1168. GlobalAtInfo.Flags |= JOB_RUN_PERIODICALLY;
  1169. } else {
  1170. return( FALSE); // an unexpected switch
  1171. }
  1172. if ( NextCount + EveryCount > 1) {
  1173. return( FALSE); // repeated switch option
  1174. }
  1175. *schedp++ = ARG_SEP_CHR;
  1176. schedp = wcstok( schedp, SCHED_TOK_DELIM);
  1177. if ( schedp == NULL) {
  1178. GlobalAtInfo.Flags |= JOB_ADD_CURRENT_DATE;
  1179. continue;
  1180. }
  1181. while( schedp != NULL) {
  1182. if ( IsDayOfMonth( schedp, &day_no) == TRUE) {
  1183. GlobalAtInfo.DaysOfMonth |= (1 << (day_no - 1));
  1184. } else if ( IsDayOfWeek( schedp, &day_no) == TRUE) {
  1185. GlobalAtInfo.DaysOfWeek |= (1 << day_no);
  1186. } else {
  1187. MessagePrint( APE_InvalidSwitchArg );
  1188. GlobalErrorReported = TRUE;
  1189. return( FALSE);
  1190. }
  1191. schedp = wcstok( NULL, SCHED_TOK_DELIM);
  1192. }
  1193. }
  1194. if ( argno == argc) {
  1195. return( FALSE); // all switches, no command
  1196. }
  1197. *pargno = argno;
  1198. return( TRUE);
  1199. }
  1200. BOOL
  1201. ParseInteractiveArg(
  1202. IN OUT WCHAR * argp
  1203. )
  1204. /*++
  1205. Routine Description:
  1206. Returns TRUE if argp is an interactive switch.
  1207. --*/
  1208. {
  1209. DWORD Value; // bitmask
  1210. if ( *argp++ != SLASH) {
  1211. return( FALSE); // not a switch
  1212. }
  1213. _wcsupr( argp); // all AT command switches can be safely uppercased
  1214. Value = MatchString( argp, AT_INTERACTIVE);
  1215. if ( Value == AT_INTERACTIVE) {
  1216. GlobalAtInfo.Flags &= ~JOB_NONINTERACTIVE; // clear noninteractive flag
  1217. return( TRUE);
  1218. }
  1219. return( FALSE); // some other switch
  1220. }
  1221. #define BUFFER_LEN 128
  1222. VOID
  1223. PrintDay(
  1224. int type,
  1225. DWORD DaysOfMonth,
  1226. UCHAR DaysOfWeek,
  1227. UCHAR Flags
  1228. )
  1229. /*++
  1230. Routine Description:
  1231. Print out schedule days. This routine converts a schedule bit map
  1232. to the literals that represent the schedule.
  1233. Arguments:
  1234. type whether this is for JobEnum or not
  1235. DaysOfMonth bitmask for days of month
  1236. DaysOfWeek bitmaks for days of week
  1237. Flags extra info about the job
  1238. Return Value:
  1239. None.
  1240. --*/
  1241. {
  1242. int i;
  1243. WCHAR Buffer[ BUFFER_LEN];
  1244. DWORD BufferLength;
  1245. DWORD Length;
  1246. DWORD TotalLength = 0;
  1247. DWORD TotalColumnLength = 0;
  1248. WCHAR * LastSpace;
  1249. DWORD MessageId;
  1250. BOOL OverFlow = TRUE;
  1251. static int Ape2GenWeekdayLong[] = {
  1252. APE2_GEN_MONDAY,
  1253. APE2_GEN_TUESDAY,
  1254. APE2_GEN_WEDNSDAY,
  1255. APE2_GEN_THURSDAY,
  1256. APE2_GEN_FRIDAY,
  1257. APE2_GEN_SATURDAY,
  1258. APE2_GEN_SUNDAY
  1259. };
  1260. static int Ape2GenWeekdayAbbrev[] = {
  1261. APE2_GEN_MONDAY_ABBREV,
  1262. APE2_GEN_TUESDAY_ABBREV,
  1263. APE2_GEN_WEDNSDAY_ABBREV,
  1264. APE2_GEN_THURSDAY_ABBREV,
  1265. APE2_GEN_FRIDAY_ABBREV,
  1266. APE2_GEN_SATURDAY_ABBREV,
  1267. APE2_GEN_SUNDAY_ABBREV
  1268. };
  1269. //
  1270. // Subtract 4 to guard against days of week or days of month overflow.
  1271. //
  1272. BufferLength = sizeof( Buffer)/ sizeof( WCHAR) - 4;
  1273. if ( type == DUMP_ALL && BufferLength > MAX_SCHED_FIELD_LENGTH) {
  1274. BufferLength = MAX_SCHED_FIELD_LENGTH;
  1275. }
  1276. //
  1277. // First do the descriptive bit (eg. EACH, NEXT, etc) with the days.
  1278. //
  1279. if ( Flags & JOB_RUN_PERIODICALLY) {
  1280. MessageId = APE2_AT_EACH;
  1281. } else if ( (DaysOfWeek != 0) || (DaysOfMonth != 0)) {
  1282. MessageId = APE2_AT_NEXT;
  1283. } else if ( Flags & JOB_RUNS_TODAY) {
  1284. MessageId = APE2_AT_TODAY;
  1285. } else {
  1286. MessageId = APE2_AT_TOMORROW;
  1287. }
  1288. Length = MessageGet(
  1289. MessageId,
  1290. (LPWSTR *) &Buffer[TotalLength],
  1291. BufferLength
  1292. );
  1293. if ( Length == 0) {
  1294. goto PrintDay_exit; // Assume this is due to lack of space
  1295. }
  1296. TotalColumnLength = GetStringColumn( &Buffer[TotalLength] );
  1297. TotalLength = Length;
  1298. if ( DaysOfWeek != 0) {
  1299. for ( i = 0; i < 7; i++) {
  1300. if ( ( DaysOfWeek & (1 << i)) != 0) {
  1301. if( bDBCS ) {
  1302. Length = MessageGet(
  1303. Ape2GenWeekdayLong[ i],
  1304. (LPWSTR *) &Buffer[TotalLength],
  1305. BufferLength - TotalLength
  1306. );
  1307. } else {
  1308. Length = MessageGet(
  1309. Ape2GenWeekdayAbbrev[ i],
  1310. (LPWSTR *) &Buffer[TotalLength],
  1311. BufferLength - TotalLength
  1312. );
  1313. }
  1314. if ( Length == 0) {
  1315. //
  1316. // Not enough room for WeekDay symbol
  1317. //
  1318. goto PrintDay_exit;
  1319. }
  1320. //
  1321. // Get how many columns will be needed for display.
  1322. //
  1323. TotalColumnLength += GetStringColumn( &Buffer[TotalLength] );
  1324. if ( TotalColumnLength >= BufferLength) {
  1325. //
  1326. // Not enough room for space following WeekDay symbol
  1327. //
  1328. goto PrintDay_exit;
  1329. }
  1330. TotalLength +=Length;
  1331. Buffer[ TotalLength++] = BLANK;
  1332. TotalColumnLength++;
  1333. }
  1334. }
  1335. }
  1336. if ( DaysOfMonth != 0) {
  1337. for ( i = 0; i < 31; i++) {
  1338. if ( ( DaysOfMonth & (1L << i)) != 0) {
  1339. Length = StringCchPrintfW(
  1340. &Buffer[ TotalLength], BUFFER_LEN - TotalLength,
  1341. L"%d ",
  1342. i + 1
  1343. );
  1344. if ( TotalLength + Length > BufferLength) {
  1345. //
  1346. // Not enough room for MonthDay symbol followed by space
  1347. //
  1348. goto PrintDay_exit;
  1349. }
  1350. TotalLength +=Length;
  1351. TotalColumnLength +=Length;
  1352. }
  1353. }
  1354. }
  1355. OverFlow = FALSE;
  1356. PrintDay_exit:
  1357. Buffer[ TotalLength] = NULLC;
  1358. if ( OverFlow == TRUE) {
  1359. if ( TotalLength > 0 && Buffer[ TotalLength - 1] == BLANK) {
  1360. //
  1361. // Eliminate trailing space if there is one.
  1362. //
  1363. Buffer[ TotalLength - 1] = NULLC;
  1364. }
  1365. //
  1366. // Then get rid of the rightmost token (or even whole thing).
  1367. //
  1368. LastSpace = wcsrchr( Buffer, BLANK);
  1369. StringCchCopyW( LastSpace != NULL ? LastSpace : Buffer,
  1370. LastSpace != NULL ? (LastSpace - &Buffer[0]) : BUFFER_LEN,
  1371. ELLIPSIS);
  1372. TotalLength = wcslen( Buffer);
  1373. }
  1374. if ( type == DUMP_ALL) {
  1375. TotalColumnLength = GetStringColumn( Buffer);
  1376. while( TotalColumnLength++ < MAX_SCHED_FIELD_LENGTH) {
  1377. Buffer[ TotalLength++] = BLANK;
  1378. }
  1379. Buffer[ TotalLength] = UNICODE_NULL;
  1380. }
  1381. GenOutputArg( TEXT("%ws"), Buffer);
  1382. }
  1383. VOID
  1384. PrintLine(
  1385. VOID
  1386. )
  1387. /*++
  1388. Routine Description:
  1389. Prints a line accross screen.
  1390. Arguments:
  1391. None.
  1392. Return Value:
  1393. None.
  1394. Note:
  1395. BUGBUG Is this treatment valid for UniCode? See also LUI_PrintLine()
  1396. BUGBUG in ui\common\src\lui\lui\border.c
  1397. --*/
  1398. #define SINGLE_HORIZONTAL L'\x02d'
  1399. #define SCREEN_WIDTH 79
  1400. {
  1401. WCHAR string[ SCREEN_WIDTH + 1];
  1402. DWORD offset;
  1403. for ( offset = 0; offset < SCREEN_WIDTH; offset++) {
  1404. string[ offset] = SINGLE_HORIZONTAL;
  1405. }
  1406. string[ SCREEN_WIDTH] = NULLC;
  1407. GenOutputArg(TEXT("%ws\n"), string);
  1408. }
  1409. VOID
  1410. PrintTime(
  1411. DWORD_PTR JobTime
  1412. )
  1413. /*++
  1414. Routine Description:
  1415. Prints time of a job in HH:MM{A,P}M format.
  1416. Arguments:
  1417. JobTime - time in miliseconds (measured from midnight)
  1418. Return Value:
  1419. None.
  1420. Note:
  1421. BUGBUG this does not make sure that JobTime is within the bounds.
  1422. BUGBUG Also, there is nothing unicode about printing this output.
  1423. --*/
  1424. {
  1425. WCHAR Buffer[15];
  1426. GetTimeString( JobTime, Buffer, sizeof( Buffer)/sizeof( WCHAR) );
  1427. GenOutputArg( DUMP_FMT3, Buffer );
  1428. }
  1429. BOOL
  1430. TraverseSearchList(
  1431. IN PWCHAR String,
  1432. IN PSEARCH_LIST SearchList,
  1433. OUT PDWORD pValue
  1434. )
  1435. /*++
  1436. Routine Description:
  1437. Examines search list until it find the correct entry, then returns
  1438. the value corresponding to this entry.
  1439. Arguments:
  1440. String - string to match
  1441. SearchList - array of entries containing valid strings
  1442. pValue - value corresponding to a matching valid string
  1443. Return Value:
  1444. TRUE a matching entry was found
  1445. FALSE otherwise
  1446. --*/
  1447. {
  1448. if ( SearchList != NULL) {
  1449. for ( NOTHING; SearchList->String != NULL; SearchList++) {
  1450. if ( _wcsicmp( String, SearchList->String) == 0) {
  1451. *pValue = SearchList->Value;
  1452. return( TRUE) ;
  1453. }
  1454. }
  1455. }
  1456. return( FALSE) ;
  1457. }
  1458. VOID
  1459. Usage(
  1460. BOOL GoodCommand
  1461. )
  1462. /*++
  1463. Routine Description:
  1464. Usage of AT command.
  1465. Arguments:
  1466. GoodCommand - TRUE if we have a good command input (request for help)
  1467. FALSE if we have a bad command input
  1468. Return Value:
  1469. None.
  1470. --*/
  1471. {
  1472. if ( GlobalErrorReported == TRUE) {
  1473. PutNewLine();
  1474. } else if ( GoodCommand == FALSE) {
  1475. MessagePrint( IDS_INVALID_COMMAND );
  1476. }
  1477. MessagePrint( IDS_USAGE );
  1478. }
  1479. #define REG_SCHEDULE_PARMS TEXT("System\\CurrentControlSet\\Services\\Schedule\\Parameters")
  1480. #define REG_SCHEDULE_USE_OLD TEXT("UseOldParsing")
  1481. BOOL
  1482. UseOldParsing()
  1483. /*++
  1484. Routine Description:
  1485. Checks the registry for
  1486. HKLM\CurrentControlSet\Services\Schedule\parameters\UseOldParsing
  1487. If present and equal to 1, then revert to 3.51 level of command line
  1488. parsing. Spaces in filenames will not work with this option. This is
  1489. intended as a migration path for customers who cannot change all their
  1490. command scripts that use AT.EXE right away.
  1491. --*/
  1492. {
  1493. BOOL fUseOld = FALSE;
  1494. LONG err = 0;
  1495. do { // Error breakout loop
  1496. HKEY hkeyScheduleParms;
  1497. DWORD dwType;
  1498. DWORD dwData = 0;
  1499. DWORD cbData = sizeof(dwData);
  1500. // Break out on any error and use the default, FALSE.
  1501. if (err = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  1502. REG_SCHEDULE_PARMS,
  1503. 0,
  1504. KEY_READ,
  1505. &hkeyScheduleParms))
  1506. {
  1507. break;
  1508. }
  1509. if (err = RegQueryValueEx(hkeyScheduleParms,
  1510. REG_SCHEDULE_USE_OLD,
  1511. NULL,
  1512. &dwType,
  1513. (LPBYTE)&dwData,
  1514. &cbData ))
  1515. {
  1516. RegCloseKey( hkeyScheduleParms );
  1517. break;
  1518. }
  1519. if ( dwType == REG_DWORD && dwData == 1 )
  1520. {
  1521. fUseOld = TRUE;
  1522. }
  1523. RegCloseKey( hkeyScheduleParms );
  1524. } while (FALSE) ;
  1525. return fUseOld;
  1526. }
  1527. BOOL
  1528. ValidateCommand(
  1529. IN int argc,
  1530. IN WCHAR ** argv,
  1531. OUT int * pCommand
  1532. )
  1533. /*++
  1534. Routine Description:
  1535. Examines command line to see what to do. This validates the command
  1536. line passed into the AT command processor. If this routine finds any
  1537. invalid data, the program exits with an appropriate error message.
  1538. Arguments:
  1539. pCommand - pointer to command
  1540. argc - count of arguments
  1541. argv - pointer to table of arguments
  1542. Return Value:
  1543. FALSE - if failure, i.e. command will not be executed
  1544. TRUE - if success
  1545. Comment:
  1546. Parsing assumes:
  1547. non-switch (positional) parameters come first and order among these
  1548. parameters is important
  1549. switch parameters come second and order among these parameters is
  1550. NOT important
  1551. command (if present) comes last
  1552. --*/
  1553. {
  1554. int i; // loop index
  1555. int next; // index of Time or JobId argument
  1556. int argno; // where to start in arg string
  1557. BOOL DeleteFound; // did we find a delete switch
  1558. WCHAR * recdatap; // ptr used to build atr_command
  1559. DWORD recdata_len; // len of arg to put in atr_command
  1560. DWORD_PTR JobTime;
  1561. BOOL fUseOldParsing = FALSE;
  1562. if (argc == 1) {
  1563. *pCommand = DUMP_ALL;
  1564. return( TRUE);
  1565. }
  1566. // First look for a help switch on the command line.
  1567. for ( i = 1; i < argc; i++ ) {
  1568. if ( !_wcsicmp( argv[i], QUESTION_SW)
  1569. || !_wcsicmp( argv[i], QUESTION_SW_TOO)) {
  1570. *pCommand = ACTION_USAGE;
  1571. return( TRUE);
  1572. }
  1573. }
  1574. next = ( ArgIsServerName( argv[ 1]) == TRUE) ? 2 : 1;
  1575. if ( argc == next) {
  1576. *pCommand = DUMP_ALL;
  1577. return( TRUE);
  1578. }
  1579. if ( (ArgIsDecimalString( argv[ next], &GlobalJobId)) == TRUE) {
  1580. if ( argc == next + 1) {
  1581. *pCommand = DUMP_ID;
  1582. return( TRUE);
  1583. }
  1584. if ( ParseJobIdArgs( argv, argc, next + 1, &DeleteFound) == FALSE) {
  1585. return( FALSE); // an invalid argument
  1586. }
  1587. *pCommand = (DeleteFound == FALSE) ? DUMP_ID : DEL_ID;
  1588. return( TRUE);
  1589. }
  1590. //
  1591. // Try some variation of "AT [\\ServerName [/DELETE]"
  1592. //
  1593. if ( ParseJobIdArgs( argv, argc, next, &DeleteFound) == TRUE) {
  1594. *pCommand = (DeleteFound == FALSE) ? DUMP_ALL : DEL_ALL;
  1595. return( TRUE);
  1596. }
  1597. if ( ArgIsTime( argv[ next], &JobTime) == TRUE) {
  1598. *pCommand = ADD_TO_SCHEDULE;
  1599. if ( argc < next + 2) {
  1600. return( FALSE); // need something to do, not just time
  1601. }
  1602. memset( (PBYTE)&GlobalAtInfo, '\0', sizeof(GlobalAtInfo)); // initialize
  1603. GlobalAtInfo.Flags |= JOB_NONINTERACTIVE; // the default
  1604. if ( ParseInteractiveArg( argv[ next + 1])) {
  1605. next++;
  1606. }
  1607. if ( argc < next + 2) {
  1608. return( FALSE); // once more with feeling
  1609. }
  1610. if ( ParseTimeArgs( argv, argc, next + 1, &argno) == FALSE) {
  1611. return( FALSE);
  1612. }
  1613. // Copy argument strings to record.
  1614. recdatap = GlobalAtInfo.Command = GlobalAtInfoCommand;
  1615. recdata_len = 0;
  1616. recdatap[0] = L'\0';
  1617. fUseOldParsing = UseOldParsing();
  1618. for ( i = argno; i < argc; i++) {
  1619. DWORD temp;
  1620. //
  1621. // Fix for bug 22068 "AT command does not handle filenames with
  1622. // spaces." The command processor takes a quoted command line arg
  1623. // and puts everything between the quotes into one argv string.
  1624. // The quotes are stripped out. Thus, if any of the string args
  1625. // contain whitespace, then they must be requoted before being
  1626. // concatenated into the command value.
  1627. //
  1628. BOOL fQuote = (!fUseOldParsing && wcschr(argv[i], L' ') != NULL);
  1629. temp = wcslen(argv[i]) + (fQuote ? 3 : 1); // add 2 for quotes
  1630. recdata_len += temp;
  1631. if ( recdata_len > MAX_COMMAND_LEN) {
  1632. MessagePrint( APE_AT_COMMAND_TOO_LONG );
  1633. return( FALSE);
  1634. }
  1635. if (fQuote)
  1636. {
  1637. StringCchCopyW(recdatap, MAX_COMMAND_LEN + 1 - wcslen(recdatap), L"\"");
  1638. StringCchCatW(recdatap, MAX_COMMAND_LEN + 1 - wcslen(recdatap), argv[i]);
  1639. StringCchCatW(recdatap, MAX_COMMAND_LEN + 1 - wcslen(recdatap), L"\"");
  1640. }
  1641. else
  1642. {
  1643. StringCchCopyW(recdatap, MAX_COMMAND_LEN + 1 - wcslen(recdatap), argv[i]);
  1644. }
  1645. recdatap += temp;
  1646. // To construct lpszCommandLine argument to CreateProcess call
  1647. // we replace nuls with spaces.
  1648. *(recdatap - 1) = BLANK;
  1649. }
  1650. // Reset space back to null on last argument in string.
  1651. *(recdatap - 1) = NULLC;
  1652. GlobalAtInfo.JobTime = JobTime;
  1653. return( TRUE);
  1654. }
  1655. return( FALSE);
  1656. }
  1657. VOID
  1658. GetTimeString(
  1659. DWORD_PTR Time,
  1660. WCHAR *Buffer,
  1661. int BufferLength
  1662. )
  1663. /*++
  1664. Routine Description:
  1665. This function converts a dword time to an ASCII string.
  1666. Arguments:
  1667. Time - Time difference in dword from start of the day (i.e. 12am
  1668. midnight ) in milliseconds
  1669. Buffer - Pointer to the buffer to place the ASCII representation.
  1670. BufferLength - The length of buffer in bytes.
  1671. Return Value:
  1672. None.
  1673. --*/
  1674. #define MINUTES_IN_HOUR 60
  1675. #define SECONDS_IN_MINUTE 60
  1676. {
  1677. WCHAR szTimeString[MAX_TIME_SIZE];
  1678. WCHAR *p = &szTimeString[1];
  1679. DWORD_PTR seconds, minutes, hours;
  1680. int numChars;
  1681. DWORD flags;
  1682. SYSTEMTIME st;
  1683. GetSystemTime(&st);
  1684. *p = NULLC;
  1685. // Check if the time format is initialized. If not, initialize it.
  1686. if ( GlobalTimeFormat.AMString == NULL )
  1687. NetpGetTimeFormat( &GlobalTimeFormat );
  1688. // Convert the time to hours, minutes, seconds
  1689. seconds = (Time/1000);
  1690. hours = seconds / (MINUTES_IN_HOUR * SECONDS_IN_MINUTE );
  1691. seconds -= hours * MINUTES_IN_HOUR * SECONDS_IN_MINUTE;
  1692. minutes = seconds / SECONDS_IN_MINUTE;
  1693. seconds -= minutes * SECONDS_IN_MINUTE;
  1694. st.wHour = (WORD)(hours);
  1695. st.wMinute = (WORD)(minutes);
  1696. st.wSecond = (WORD)(seconds);
  1697. st.wMilliseconds = 0;
  1698. flags = TIME_NOSECONDS;
  1699. if (!GlobalTimeFormat.TwelveHour)
  1700. flags |= TIME_FORCE24HOURFORMAT;
  1701. numChars = GetTimeFormatW(GetThreadLocale(),
  1702. flags, &st, NULL, p, MAX_TIME_SIZE-1);
  1703. if ( numChars > BufferLength )
  1704. numChars = BufferLength;
  1705. if (*(p+1) == ARG_SEP_CHR && GlobalTimeFormat.LeadingZero) {
  1706. *(--p) = TEXT('0');
  1707. numChars++;
  1708. }
  1709. wcsncpy( Buffer, p, numChars );
  1710. // Append spece for align print format. column based.
  1711. {
  1712. DWORD ColumnLength;
  1713. // character counts -> array index.
  1714. numChars--;
  1715. ColumnLength = GetStringColumn( Buffer );
  1716. while( ColumnLength++ < MAX_TIME_FIELD_LENGTH) {
  1717. Buffer[ numChars++] = BLANK;
  1718. }
  1719. Buffer[ numChars] = UNICODE_NULL;
  1720. }
  1721. }
  1722. BOOL
  1723. InitList( PSEARCH_LIST SearchList )
  1724. {
  1725. if ( SearchList != NULL) {
  1726. if ( SearchList->String != NULL ) // Already initialized
  1727. return TRUE;
  1728. for ( NOTHING; SearchList->MessageId != 0; SearchList++) {
  1729. if ( MessageGet( SearchList->MessageId,
  1730. &SearchList->String,
  1731. 0 ) == 0 )
  1732. {
  1733. return FALSE;
  1734. }
  1735. }
  1736. }
  1737. return TRUE;
  1738. }
  1739. VOID
  1740. TermList( PSEARCH_LIST SearchList )
  1741. {
  1742. if ( SearchList != NULL) {
  1743. if ( SearchList->String == NULL ) // Not initialized
  1744. return;
  1745. for ( NOTHING; SearchList->String != NULL; SearchList++) {
  1746. LocalFree( SearchList->String );
  1747. }
  1748. }
  1749. }
  1750. DWORD
  1751. GetStringColumn( WCHAR *lpwstr )
  1752. {
  1753. int cchNeed;
  1754. cchNeed = WideCharToMultiByte( GetConsoleOutputCP() , 0 ,
  1755. lpwstr , -1 ,
  1756. NULL , 0 ,
  1757. NULL , NULL );
  1758. return( (DWORD) cchNeed - 1 ); // - 1 : remove NULL
  1759. }