Source code of Windows XP (NT5)
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.

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