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.

2294 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 (MAX_PATH - 1) // == 259, based on value used in scheduler service for manipulating AT jobs
  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 DWORD_PTR *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_PTR 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_PTR 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 = (fgets(oemBuf, 16, stdin) != 0);
  473. #if DBG
  474. fprintf(stderr, "got >%s<\n", oemBuf);
  475. #endif
  476. cch = 0;
  477. if (retc) {
  478. if (strchr(oemBuf, '\n')) {
  479. *strchr(oemBuf, '\n') = '\0';
  480. }
  481. cch = MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED,
  482. oemBuf, strlen(oemBuf)+1, rbuf, 16);
  483. }
  484. }
  485. #if DBG
  486. fprintf(stderr, "cch = %d, retc = %d\n", cch, retc);
  487. #endif
  488. if (!retc || cch == 0)
  489. return( FALSE);
  490. #if DBG
  491. fprintf(stderr, "converted to >%ws<\n", rbuf);
  492. #endif
  493. Value = MatchString(_wcsupr(rbuf), AT_CONFIRM_NO_VALUE | AT_CONFIRM_YES_VALUE);
  494. if ( Value == AT_CONFIRM_NO_VALUE) {
  495. return( FALSE);
  496. } else if ( Value == AT_CONFIRM_YES_VALUE) {
  497. break;
  498. }
  499. if ( ++retries >= 3) {
  500. MessagePrint( APE_NoGoodResponse );
  501. return( FALSE);
  502. }
  503. if ( MessagePrint( APE_UtilInvalidResponse ) == 0) {
  504. exit( AT_GENERIC_ERROR);
  505. }
  506. }
  507. return( TRUE);
  508. }
  509. BOOL
  510. ArgIsServerName(
  511. WCHAR * string
  512. )
  513. /*++
  514. Routine Description:
  515. Checks if string is a server name. Validation is really primitive, eg
  516. strings like "\\\threeslashes" pass the test.
  517. Arguments:
  518. string - pointer to string that may represent a server name
  519. Return Value:
  520. TRUE - string is (or might be) a valid server name
  521. FALSE - string is not a valid server name
  522. --*/
  523. {
  524. NET_API_STATUS ApiStatus;
  525. if (string[0] == BACKSLASH && string[1] == BACKSLASH && string[2] != 0) {
  526. ApiStatus = NetpNameValidate(
  527. NULL, // no server name.
  528. &string[2], // name to validate
  529. NAMETYPE_COMPUTER,
  530. LM2X_COMPATIBLE); // flags
  531. if (ApiStatus != NO_ERROR) {
  532. return (FALSE);
  533. }
  534. GlobalServerName = string;
  535. return( TRUE);
  536. }
  537. return( FALSE); // GlobalServerName is NULL at load time
  538. }
  539. BOOL
  540. ArgIsTime(
  541. IN WCHAR * timestr,
  542. OUT DWORD_PTR *pJobTime
  543. )
  544. /*++
  545. Routine Description:
  546. Determines whether string is a time or not. Validates that string
  547. passed into it is in the form of HH:MM. It searches the string for
  548. a ":" and then validates that the preceeding data is numeric & in a
  549. valid range for hours. It then validates the string after the ":"
  550. is numeric & in a validate range for minutes. If all the tests are
  551. passed the TRUE is returned.
  552. Arguments:
  553. timestr - string to check whether it is a time
  554. JobTime - ptr to number of miliseconds
  555. Return Value:
  556. TRUE - timestr was a time in HH:MM format
  557. FALSE - timestr wasn't at time
  558. --*/
  559. {
  560. CHAR buffer[MAX_TIME_SIZE];
  561. USHORT ParseLen;
  562. BOOL fDummy;
  563. if ( timestr == NULL )
  564. return FALSE;
  565. if ( !WideCharToMultiByte( CP_ACP,
  566. 0,
  567. timestr,
  568. -1,
  569. buffer,
  570. sizeof( buffer )/sizeof(CHAR),
  571. NULL,
  572. &fDummy ))
  573. {
  574. return FALSE;
  575. }
  576. if ( LUI_ParseTimeSinceStartOfDay( buffer, pJobTime, &ParseLen, 0) )
  577. return FALSE;
  578. // LUI_ParseTimeSinceStartOfDay returns the time in seconds.
  579. // Hence, we need to convert it to microseconds.
  580. *pJobTime *= 1000;
  581. return( TRUE);
  582. }
  583. BOOL
  584. ArgIsDecimalString(
  585. IN WCHAR * pDecimalString,
  586. OUT PDWORD pNumber
  587. )
  588. /*++
  589. Routine Description:
  590. This routine converts a string into a DWORD if it possibly can.
  591. The conversion is successful if string is decimal numeric and
  592. does not lead to an overflow.
  593. Arguments:
  594. pDecimalString ptr to decimal string
  595. pNumber ptr to number
  596. Return Value:
  597. FALSE invalid number
  598. TRUE valid number
  599. --*/
  600. {
  601. DWORD Value;
  602. DWORD OldValue;
  603. DWORD digit;
  604. if ( pDecimalString == NULL || *pDecimalString == 0) {
  605. return( FALSE);
  606. }
  607. Value = 0;
  608. while ( (digit = *pDecimalString++) != 0) {
  609. if ( digit < L'0' || digit > L'9') {
  610. return( FALSE); // not a decimal string
  611. }
  612. OldValue = Value;
  613. Value = digit - L'0' + 10 * Value;
  614. if ( Value < OldValue) {
  615. return( FALSE); // overflow
  616. }
  617. }
  618. *pNumber = Value;
  619. return( TRUE);
  620. }
  621. BOOL
  622. IsDayOfMonth(
  623. IN WCHAR * pToken,
  624. OUT PDWORD pDay
  625. )
  626. /*++
  627. Routine Description:
  628. Converts a string into a number for the day of the month, if it can
  629. possibly do so. Note that "first" == 1, ...
  630. Arguments:
  631. pToken pointer to schedule token for the day of the month
  632. pDay pointer to index of day in a month
  633. Return Value:
  634. TRUE if a valid schedule token
  635. FALSE otherwise
  636. --*/
  637. {
  638. return ( ArgIsDecimalString( pToken, pDay) == TRUE && *pDay >= 1
  639. && *pDay <= 31);
  640. }
  641. BOOL
  642. IsDayOfWeek(
  643. WCHAR * pToken,
  644. PDWORD pDay
  645. )
  646. /*++
  647. Routine Description:
  648. This routine converts a string day of the week into a integer
  649. offset into the week if it possibly can. Note that Monday==0,
  650. ..., Sunday == 6.
  651. Arguments:
  652. pToken pointer to schedule token for the day of a week
  653. pDay pointer to index of day in a month
  654. Return Value:
  655. TRUE if a valid schedule token
  656. FALSE otherwise
  657. --*/
  658. {
  659. if ( !InitList( DaysOfWeekSearchList ) )
  660. {
  661. // Error already reported
  662. exit( -1 );
  663. }
  664. return( TraverseSearchList(
  665. pToken,
  666. DaysOfWeekSearchList,
  667. pDay
  668. ));
  669. }
  670. NET_API_STATUS
  671. JobAdd(
  672. VOID
  673. )
  674. /*++
  675. Routine Description:
  676. Adds a new item to schedule.
  677. Arguments:
  678. None. Uses globals.
  679. Return Value:
  680. NET_API_STATUS return value of remote api call
  681. --*/
  682. {
  683. NET_API_STATUS status;
  684. for ( ; ; ) {
  685. status = NetScheduleJobAdd(
  686. GlobalServerName,
  687. (LPBYTE)&GlobalAtInfo,
  688. &GlobalJobId
  689. );
  690. if ( status == ERROR_INVALID_PARAMETER &&
  691. GlobalAtInfo.Flags & JOB_NONINTERACTIVE) {
  692. //
  693. // We may have failed because we are talking to a down level
  694. // server that does not know about JOB_NONINTERACTIVE bit.
  695. // Clear the bit, and try again.
  696. // A better approach would be to check the version of the
  697. // server before making NetScheduleJobAdd() call, adjust the
  698. // bit appropriately and only then call NetScheduleJobAdd().
  699. //
  700. GlobalAtInfo.Flags &= ~JOB_NONINTERACTIVE;
  701. } else {
  702. break;
  703. }
  704. }
  705. if ( status == NERR_Success) {
  706. MessagePrint( IDS_ADD_NEW_JOB, GlobalJobId );
  707. } else {
  708. if ( MessagePrint( status ) == 0) {
  709. exit( AT_GENERIC_ERROR);
  710. }
  711. }
  712. return( status);
  713. }
  714. NET_API_STATUS
  715. JobEnum(
  716. VOID
  717. )
  718. /*++
  719. Routine Description:
  720. This does all of the processing necessary to dump out the entire
  721. schedule file. It loops through on each record and formats its
  722. information for printing and then goes to the next.
  723. Arguments:
  724. None. Uses globals.
  725. Return Value:
  726. ERROR_SUCCESS if everything enumerated OK
  727. error returned by remote api otherwise
  728. --*/
  729. {
  730. BOOL first = TRUE;
  731. DWORD ResumeJobId = 0;
  732. NET_API_STATUS status = NERR_Success;
  733. PAT_ENUM pAtEnum;
  734. DWORD EntriesRead;
  735. DWORD TotalEntries;
  736. LPVOID EnumBuffer;
  737. DWORD length;
  738. WCHAR * smallBuffer = NULL;
  739. for ( ; ;) {
  740. status = NetScheduleJobEnum(
  741. GlobalServerName,
  742. (LPBYTE *)&EnumBuffer,
  743. (DWORD)-1,
  744. &EntriesRead,
  745. &TotalEntries,
  746. &ResumeJobId
  747. );
  748. if ( status != ERROR_SUCCESS && status != ERROR_MORE_DATA) {
  749. length = MessagePrint( status );
  750. if ( length == 0) {
  751. exit( AT_GENERIC_ERROR);
  752. }
  753. return( status);
  754. }
  755. ASSERT( status == ERROR_SUCCESS ? TotalEntries == EntriesRead
  756. : TotalEntries > EntriesRead);
  757. if ( TotalEntries == 0) {
  758. break; // no items found
  759. }
  760. if ( first == TRUE) {
  761. length = MessagePrint( APE2_AT_DUMP_HEADER );
  762. if ( length == 0) {
  763. exit( AT_GENERIC_ERROR);
  764. }
  765. PrintLine(); // line across screen
  766. first = FALSE;
  767. }
  768. for ( pAtEnum = EnumBuffer; EntriesRead-- > 0; pAtEnum++) {
  769. if ( pAtEnum->Flags & JOB_EXEC_ERROR) {
  770. if ( MessageGet( APE2_GEN_ERROR, &smallBuffer, 0 ) == 0) {
  771. // error reported already
  772. exit( AT_GENERIC_ERROR);
  773. }
  774. GenOutputArg( DUMP_FMT1, smallBuffer );
  775. LocalFree( smallBuffer );
  776. } else {
  777. GenOutputArg( DUMP_FMT1, L"");
  778. }
  779. GenOutputArg( DUMP_FMT2, pAtEnum->JobId);
  780. PrintDay( DUMP_ALL, pAtEnum->DaysOfMonth, pAtEnum->DaysOfWeek,
  781. pAtEnum->Flags);
  782. PrintTime( pAtEnum->JobTime);
  783. GenOutputArg( TEXT("%ws\n"), pAtEnum->Command);
  784. }
  785. if ( EnumBuffer != NULL) {
  786. (VOID)NetApiBufferFree( (LPVOID)EnumBuffer);
  787. EnumBuffer = NULL;
  788. }
  789. if ( status == ERROR_SUCCESS) {
  790. break; // we have read & displayed all the items
  791. }
  792. }
  793. if ( first == TRUE) {
  794. MessagePrint( APE_EmptyList );
  795. }
  796. return( ERROR_SUCCESS);
  797. }
  798. NET_API_STATUS
  799. JobGetInfo(
  800. VOID
  801. )
  802. /*++
  803. Routine Description:
  804. This prints out the schedule of an individual items schedule.
  805. Arguments:
  806. None. Uses globals.
  807. Return Value:
  808. NET_API_STATUS value returned by remote api
  809. --*/
  810. {
  811. PAT_INFO pAtInfo = NULL;
  812. NET_API_STATUS status;
  813. status = NetScheduleJobGetInfo(
  814. GlobalServerName,
  815. GlobalJobId,
  816. (LPBYTE *)&pAtInfo
  817. );
  818. if ( status != NERR_Success) {
  819. MessagePrint( status );
  820. return( status);
  821. }
  822. PutNewLine();
  823. MessagePrint( APE2_AT_DI_TASK );
  824. GenOutputArg( TEXT("%d"), GlobalJobId);
  825. PutNewLine();
  826. MessagePrint( APE2_AT_DI_STATUS );
  827. MessagePrint( (pAtInfo->Flags & JOB_EXEC_ERROR) != 0 ?
  828. APE2_GEN_ERROR : APE2_GEN_OK );
  829. PutNewLine();
  830. MessagePrint( APE2_AT_DI_SCHEDULE );
  831. PrintDay( DUMP_ID, pAtInfo->DaysOfMonth, pAtInfo->DaysOfWeek,
  832. pAtInfo->Flags);
  833. PutNewLine();
  834. MessagePrint( APE2_AT_DI_TIMEOFDAY );
  835. PrintTime( pAtInfo->JobTime);
  836. PutNewLine();
  837. MessagePrint( APE2_AT_DI_INTERACTIVE);
  838. MessagePrint( (pAtInfo->Flags & JOB_NONINTERACTIVE) == 0 ?
  839. APE2_GEN_YES : APE2_GEN_NO );
  840. PutNewLine();
  841. MessagePrint( APE2_AT_DI_COMMAND );
  842. GenOutputArg( TEXT("%ws\n"), pAtInfo->Command);
  843. PutNewLine2();
  844. (VOID)NetApiBufferFree( (LPVOID)pAtInfo);
  845. return( NERR_Success);
  846. }
  847. DWORD
  848. MatchString(
  849. WCHAR * name,
  850. DWORD Values
  851. )
  852. /*++
  853. Routine Description:
  854. Parses switch string and returns NULL for an invalid switch,
  855. and -1 for an ambiguous switch.
  856. Arguments:
  857. name - pointer to string we need to examine
  858. Values - bitmask of values of interest
  859. Return Value:
  860. Pointer to command, or NULL or -1.
  861. --*/
  862. {
  863. WCHAR * String;
  864. PSEARCH_LIST pCurrentList;
  865. WCHAR * CurrentString;
  866. DWORD FoundValue;
  867. int nmatches;
  868. int longest;
  869. if ( !InitList( GlobalListTable ) )
  870. {
  871. // Error already reported
  872. exit( -1 );
  873. }
  874. for ( pCurrentList = GlobalListTable,
  875. longest = nmatches = 0,
  876. FoundValue = 0;
  877. (CurrentString = pCurrentList->String) != NULL;
  878. pCurrentList++) {
  879. if ( (Values & pCurrentList->Value) == 0) {
  880. continue; // skip this List
  881. }
  882. for ( String = name; *String == *CurrentString++; String++) {
  883. if ( *String == 0) {
  884. return( pCurrentList->Value); // exact match
  885. }
  886. }
  887. if ( !*String) {
  888. if ( String - name > longest) {
  889. longest = (int)(String - name);
  890. nmatches = 1;
  891. FoundValue = pCurrentList->Value;
  892. } else if ( String - name == longest) {
  893. nmatches++;
  894. }
  895. }
  896. }
  897. // 0 corresponds to no match at all (invalid List)
  898. // while -1 corresponds to multiple match (ambiguous List).
  899. if ( nmatches != 1) {
  900. return ( (nmatches == 0) ? 0 : -1);
  901. }
  902. return( FoundValue);
  903. }
  904. DWORD
  905. MessageGet(
  906. IN DWORD MessageId,
  907. OUT LPWSTR *buffer,
  908. IN DWORD Size
  909. )
  910. /*++
  911. Routine Description:
  912. Fills in the unicode message corresponding to a given message id,
  913. provided that a message can be found and that it fits in a supplied
  914. buffer.
  915. Arguments:
  916. MessageId - message id
  917. buffer - pointer to caller supplied buffer
  918. Size - size (always in bytes) of supplied buffer,
  919. If size is 0, buffer will be allocated by FormatMessage.
  920. Return Value:
  921. Count of characters, not counting the terminating null character,
  922. returned in the buffer. Zero return value indicates failure.
  923. --*/
  924. {
  925. DWORD length;
  926. LPVOID lpSource;
  927. DWORD dwFlags;
  928. if ( MessageId < NERR_BASE) {
  929. //
  930. // Get message from system.
  931. //
  932. lpSource = NULL; // redundant step according to FormatMessage() spec
  933. dwFlags = FORMAT_MESSAGE_FROM_SYSTEM;
  934. } else if ( ( MessageId >= APE2_AT_DEL_WARNING
  935. && MessageId <= APE2_AT_DI_INTERACTIVE)
  936. || ( MessageId >= IDS_LOAD_LIBRARY_FAILURE
  937. && MessageId <= IDS_INTERACTIVE )) {
  938. //
  939. // Get message from this module.
  940. //
  941. lpSource = NULL;
  942. dwFlags = FORMAT_MESSAGE_FROM_HMODULE;
  943. } else {
  944. //
  945. // Get message from netmsg.dll.
  946. //
  947. lpSource = GlobalMessageHandle;
  948. dwFlags = FORMAT_MESSAGE_FROM_HMODULE;
  949. }
  950. if ( Size == 0 )
  951. dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER;
  952. length = FormatMessage(
  953. dwFlags, // dwFlags
  954. lpSource, // lpSource
  955. MessageId, // MessageId
  956. 0, // dwLanguageId
  957. (LPWSTR) buffer, // lpBuffer
  958. Size, // nSize
  959. NULL // lpArguments
  960. );
  961. if ( length == 0) {
  962. MessagePrint( IDS_MESSAGE_GET_ERROR, MessageId, GetLastError());
  963. }
  964. return( length);
  965. } // MessageGet()
  966. int
  967. FileIsConsole(
  968. int fh
  969. )
  970. {
  971. unsigned htype ;
  972. htype = GetFileType(GetStdHandle(fh));
  973. htype &= ~FILE_TYPE_REMOTE;
  974. return htype == FILE_TYPE_CHAR;
  975. }
  976. DWORD
  977. ConsolePrint(
  978. LPWSTR pch,
  979. int cch
  980. )
  981. {
  982. int cchOut;
  983. int err;
  984. CHAR *pchOemBuffer;
  985. if (FileIsConsole(STD_OUTPUT_HANDLE)) {
  986. err = WriteConsole(
  987. GetStdHandle(STD_OUTPUT_HANDLE),
  988. pch, cch,
  989. &cchOut, NULL);
  990. if (!err || cchOut != cch)
  991. goto try_again;
  992. }
  993. else if ( cch != 0) {
  994. try_again:
  995. cchOut = WideCharToMultiByte(CP_OEMCP, 0, pch, cch, NULL, 0, NULL,NULL);
  996. if (cchOut == 0)
  997. return 0;
  998. if ((pchOemBuffer = (CHAR *)malloc(cchOut)) != NULL) {
  999. WideCharToMultiByte(CP_OEMCP, 0, pch, cch,
  1000. pchOemBuffer, cchOut, NULL, NULL);
  1001. WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
  1002. pchOemBuffer, cchOut, &cch, NULL);
  1003. free(pchOemBuffer);
  1004. }
  1005. }
  1006. return cchOut;
  1007. }
  1008. DWORD
  1009. MessagePrint(
  1010. IN DWORD MessageId,
  1011. ...
  1012. )
  1013. /*++
  1014. Routine Description:
  1015. Finds the unicode message corresponding to the supplied message id,
  1016. merges it with caller supplied string(s), and prints the resulting
  1017. string.
  1018. Arguments:
  1019. MessageId - message id
  1020. Return Value:
  1021. Count of characters, not counting the terminating null character,
  1022. printed by this routine. Zero return value indicates failure.
  1023. --*/
  1024. {
  1025. va_list arglist;
  1026. WCHAR * buffer = NULL;
  1027. DWORD length;
  1028. LPVOID lpSource;
  1029. DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER;
  1030. va_start( arglist, MessageId );
  1031. if ( MessageId < NERR_BASE) {
  1032. //
  1033. // Get message from system.
  1034. //
  1035. lpSource = NULL; // redundant step according to FormatMessage() spec
  1036. dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM;
  1037. } else if ( ( MessageId >= APE2_AT_DEL_WARNING
  1038. && MessageId <= APE2_AT_DI_INTERACTIVE)
  1039. || ( MessageId >= IDS_LOAD_LIBRARY_FAILURE
  1040. && MessageId <= IDS_INTERACTIVE )) {
  1041. //
  1042. // Get message from this module.
  1043. //
  1044. lpSource = NULL;
  1045. dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
  1046. } else {
  1047. //
  1048. // Get message from netmsg.dll.
  1049. //
  1050. lpSource = GlobalMessageHandle;
  1051. dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
  1052. }
  1053. length = FormatMessage(
  1054. dwFlags, // dwFlags
  1055. lpSource, // lpSource
  1056. MessageId, // MessageId
  1057. 0L, // dwLanguageId
  1058. (LPTSTR)&buffer, // lpBuffer
  1059. 0, // size
  1060. &arglist // lpArguments
  1061. );
  1062. length = ConsolePrint(buffer, length);
  1063. LocalFree(buffer);
  1064. return( length);
  1065. } // MessagePrint()
  1066. BOOL
  1067. ParseJobIdArgs(
  1068. WCHAR ** argv,
  1069. int argc,
  1070. int argno,
  1071. PBOOL pDeleteFound
  1072. )
  1073. /*++
  1074. Routine Description:
  1075. Parses arguments for commands containing JobId (these can be JobGetInfo
  1076. and JobDel commands). It loops through JobId arguments making sure that
  1077. we have at most one "yes-no" switch and at most one "delete" switch and
  1078. nothing else.
  1079. Arguments:
  1080. argv argument list
  1081. argc number of arguments to parse
  1082. argno index of argument to begin parsing from
  1083. pDeleteFound did we find a delete switch or not
  1084. Return Value:
  1085. FALSE invalid argument found
  1086. TRUE valid arguments
  1087. --*/
  1088. {
  1089. BOOL FoundDeleteSwitch;
  1090. for ( FoundDeleteSwitch = FALSE; argno < argc; argno++) {
  1091. WCHAR * argp;
  1092. DWORD length;
  1093. DWORD Value;
  1094. argp = argv[ argno];
  1095. if ( *argp++ != SLASH) {
  1096. return( FALSE); // not a switch
  1097. }
  1098. _wcsupr( argp);
  1099. length = wcslen( argp);
  1100. Value = MatchString( argp, AT_YES_VALUE | AT_DELETE_VALUE);
  1101. if ( Value == AT_YES_VALUE) {
  1102. if ( GlobalYes == TRUE) {
  1103. return( FALSE); // multiple instances of yes switch
  1104. }
  1105. GlobalYes = TRUE;
  1106. continue;
  1107. }
  1108. if ( Value == AT_DELETE_VALUE) {
  1109. if ( FoundDeleteSwitch == TRUE) {
  1110. return( FALSE); // duplicate delete switch
  1111. }
  1112. FoundDeleteSwitch = TRUE;
  1113. continue;
  1114. }
  1115. return( FALSE); // an unknown switch
  1116. }
  1117. *pDeleteFound = FoundDeleteSwitch;
  1118. return( TRUE);
  1119. } // ParseJobIdArgs()
  1120. BOOL
  1121. ParseTimeArgs(
  1122. WCHAR ** argv,
  1123. int argc,
  1124. int argno,
  1125. int * pargno
  1126. )
  1127. /*++
  1128. Routine Description:
  1129. Parses arguments for command addition.
  1130. Arguments:
  1131. argv argument list
  1132. argc count of args
  1133. argno index of the first arg to validate
  1134. pargno ptr to the index of the first non-switch arg
  1135. Return Value:
  1136. TRUE all arguments are valid
  1137. FALSE otherwise
  1138. --*/
  1139. {
  1140. DWORD day_no; // day number for scheduling
  1141. DWORD NextCount = 0; // count of next switches
  1142. DWORD EveryCount = 0; // count of every switches
  1143. WCHAR * argp; // ptr to arg string
  1144. WCHAR * schedp; // work ptr to arg string
  1145. DWORD Value; // bitmask
  1146. for ( NOTHING; argno < argc; argno++) {
  1147. argp = argv[ argno];
  1148. if ( *argp++ != SLASH) {
  1149. break; // found non-switch, we are done
  1150. }
  1151. schedp = wcschr( argp, ARG_SEP_CHR);
  1152. if ( schedp == NULL) {
  1153. return( FALSE);
  1154. }
  1155. _wcsupr( argp); // upper case entire input, not just the switch name
  1156. *schedp = 0;
  1157. Value = MatchString( argp, AT_NEXT_VALUE | AT_EVERY_VALUE);
  1158. if ( Value == AT_NEXT_VALUE) {
  1159. NextCount++;
  1160. } else if ( Value == AT_EVERY_VALUE) {
  1161. EveryCount++;
  1162. GlobalAtInfo.Flags |= JOB_RUN_PERIODICALLY;
  1163. } else {
  1164. return( FALSE); // an unexpected switch
  1165. }
  1166. if ( NextCount + EveryCount > 1) {
  1167. return( FALSE); // repeated switch option
  1168. }
  1169. *schedp++ = ARG_SEP_CHR;
  1170. schedp = wcstok( schedp, SCHED_TOK_DELIM);
  1171. if ( schedp == NULL) {
  1172. GlobalAtInfo.Flags |= JOB_ADD_CURRENT_DATE;
  1173. continue;
  1174. }
  1175. while( schedp != NULL) {
  1176. if ( IsDayOfMonth( schedp, &day_no) == TRUE) {
  1177. GlobalAtInfo.DaysOfMonth |= (1 << (day_no - 1));
  1178. } else if ( IsDayOfWeek( schedp, &day_no) == TRUE) {
  1179. GlobalAtInfo.DaysOfWeek |= (1 << day_no);
  1180. } else {
  1181. MessagePrint( APE_InvalidSwitchArg );
  1182. GlobalErrorReported = TRUE;
  1183. return( FALSE);
  1184. }
  1185. schedp = wcstok( NULL, SCHED_TOK_DELIM);
  1186. }
  1187. }
  1188. if ( argno == argc) {
  1189. return( FALSE); // all switches, no command
  1190. }
  1191. *pargno = argno;
  1192. return( TRUE);
  1193. }
  1194. BOOL
  1195. ParseInteractiveArg(
  1196. IN OUT WCHAR * argp
  1197. )
  1198. /*++
  1199. Routine Description:
  1200. Returns TRUE if argp is an interactive switch.
  1201. --*/
  1202. {
  1203. DWORD Value; // bitmask
  1204. if ( *argp++ != SLASH) {
  1205. return( FALSE); // not a switch
  1206. }
  1207. _wcsupr( argp); // all AT command switches can be safely uppercased
  1208. Value = MatchString( argp, AT_INTERACTIVE);
  1209. if ( Value == AT_INTERACTIVE) {
  1210. GlobalAtInfo.Flags &= ~JOB_NONINTERACTIVE; // clear noninteractive flag
  1211. return( TRUE);
  1212. }
  1213. return( FALSE); // some other switch
  1214. }
  1215. VOID
  1216. PrintDay(
  1217. int type,
  1218. DWORD DaysOfMonth,
  1219. UCHAR DaysOfWeek,
  1220. UCHAR Flags
  1221. )
  1222. /*++
  1223. Routine Description:
  1224. Print out schedule days. This routine converts a schedule bit map
  1225. to the literals that represent the schedule.
  1226. Arguments:
  1227. type whether this is for JobEnum or not
  1228. DaysOfMonth bitmask for days of month
  1229. DaysOfWeek bitmaks for days of week
  1230. Flags extra info about the job
  1231. Return Value:
  1232. None.
  1233. --*/
  1234. {
  1235. int i;
  1236. WCHAR Buffer[ 128];
  1237. DWORD BufferLength;
  1238. DWORD Length;
  1239. DWORD TotalLength = 0;
  1240. DWORD TotalColumnLength = 0;
  1241. WCHAR * LastSpace;
  1242. DWORD MessageId;
  1243. BOOL OverFlow = TRUE;
  1244. static int Ape2GenWeekdayLong[] = {
  1245. APE2_GEN_MONDAY,
  1246. APE2_GEN_TUESDAY,
  1247. APE2_GEN_WEDNSDAY,
  1248. APE2_GEN_THURSDAY,
  1249. APE2_GEN_FRIDAY,
  1250. APE2_GEN_SATURDAY,
  1251. APE2_GEN_SUNDAY
  1252. };
  1253. static int Ape2GenWeekdayAbbrev[] = {
  1254. APE2_GEN_MONDAY_ABBREV,
  1255. APE2_GEN_TUESDAY_ABBREV,
  1256. APE2_GEN_WEDNSDAY_ABBREV,
  1257. APE2_GEN_THURSDAY_ABBREV,
  1258. APE2_GEN_FRIDAY_ABBREV,
  1259. APE2_GEN_SATURDAY_ABBREV,
  1260. APE2_GEN_SUNDAY_ABBREV
  1261. };
  1262. //
  1263. // Subtract 4 to guard against days of week or days of month overflow.
  1264. //
  1265. BufferLength = sizeof( Buffer)/ sizeof( WCHAR) - 4;
  1266. if ( type == DUMP_ALL && BufferLength > MAX_SCHED_FIELD_LENGTH) {
  1267. BufferLength = MAX_SCHED_FIELD_LENGTH;
  1268. }
  1269. //
  1270. // First do the descriptive bit (eg. EACH, NEXT, etc) with the days.
  1271. //
  1272. if ( Flags & JOB_RUN_PERIODICALLY) {
  1273. MessageId = APE2_AT_EACH;
  1274. } else if ( (DaysOfWeek != 0) || (DaysOfMonth != 0)) {
  1275. MessageId = APE2_AT_NEXT;
  1276. } else if ( Flags & JOB_RUNS_TODAY) {
  1277. MessageId = APE2_AT_TODAY;
  1278. } else {
  1279. MessageId = APE2_AT_TOMORROW;
  1280. }
  1281. Length = MessageGet(
  1282. MessageId,
  1283. (LPWSTR *) &Buffer[TotalLength],
  1284. BufferLength
  1285. );
  1286. if ( Length == 0) {
  1287. goto PrintDay_exit; // Assume this is due to lack of space
  1288. }
  1289. TotalColumnLength = GetStringColumn( &Buffer[TotalLength] );
  1290. TotalLength = Length;
  1291. if ( DaysOfWeek != 0) {
  1292. for ( i = 0; i < 7; i++) {
  1293. if ( ( DaysOfWeek & (1 << i)) != 0) {
  1294. if( bDBCS ) {
  1295. Length = MessageGet(
  1296. Ape2GenWeekdayLong[ i],
  1297. (LPWSTR *) &Buffer[TotalLength],
  1298. BufferLength - TotalLength
  1299. );
  1300. } else {
  1301. Length = MessageGet(
  1302. Ape2GenWeekdayAbbrev[ i],
  1303. (LPWSTR *) &Buffer[TotalLength],
  1304. BufferLength - TotalLength
  1305. );
  1306. }
  1307. if ( Length == 0) {
  1308. //
  1309. // Not enough room for WeekDay symbol
  1310. //
  1311. goto PrintDay_exit;
  1312. }
  1313. //
  1314. // Get how many columns will be needed for display.
  1315. //
  1316. TotalColumnLength += GetStringColumn( &Buffer[TotalLength] );
  1317. if ( TotalColumnLength >= BufferLength) {
  1318. //
  1319. // Not enough room for space following WeekDay symbol
  1320. //
  1321. goto PrintDay_exit;
  1322. }
  1323. TotalLength +=Length;
  1324. Buffer[ TotalLength++] = BLANK;
  1325. TotalColumnLength++;
  1326. }
  1327. }
  1328. }
  1329. if ( DaysOfMonth != 0) {
  1330. for ( i = 0; i < 31; i++) {
  1331. if ( ( DaysOfMonth & (1L << i)) != 0) {
  1332. Length = swprintf(
  1333. &Buffer[ TotalLength],
  1334. L"%d ",
  1335. i + 1
  1336. );
  1337. if ( TotalLength + Length > BufferLength) {
  1338. //
  1339. // Not enough room for MonthDay symbol followed by space
  1340. //
  1341. goto PrintDay_exit;
  1342. }
  1343. TotalLength +=Length;
  1344. TotalColumnLength +=Length;
  1345. }
  1346. }
  1347. }
  1348. OverFlow = FALSE;
  1349. PrintDay_exit:
  1350. Buffer[ TotalLength] = NULLC;
  1351. if ( OverFlow == TRUE) {
  1352. if ( TotalLength > 0 && Buffer[ TotalLength - 1] == BLANK) {
  1353. //
  1354. // Eliminate trailing space if there is one.
  1355. //
  1356. Buffer[ TotalLength - 1] = NULLC;
  1357. }
  1358. //
  1359. // Then get rid of the rightmost token (or even whole thing).
  1360. //
  1361. LastSpace = wcsrchr( Buffer, BLANK);
  1362. wcscpy( LastSpace != NULL ? LastSpace : Buffer, ELLIPSIS);
  1363. TotalLength = wcslen( Buffer);
  1364. }
  1365. if ( type == DUMP_ALL) {
  1366. while( TotalColumnLength++ < MAX_SCHED_FIELD_LENGTH) {
  1367. Buffer[ TotalLength++] = BLANK;
  1368. }
  1369. Buffer[ TotalLength] = UNICODE_NULL;
  1370. }
  1371. GenOutputArg( TEXT("%ws"), Buffer);
  1372. }
  1373. VOID
  1374. PrintLine(
  1375. VOID
  1376. )
  1377. /*++
  1378. Routine Description:
  1379. Prints a line accross screen.
  1380. Arguments:
  1381. None.
  1382. Return Value:
  1383. None.
  1384. Note:
  1385. BUGBUG Is this treatment valid for UniCode? See also LUI_PrintLine()
  1386. BUGBUG in ui\common\src\lui\lui\border.c
  1387. --*/
  1388. #define SINGLE_HORIZONTAL L'\x02d'
  1389. #define SCREEN_WIDTH 79
  1390. {
  1391. WCHAR string[ SCREEN_WIDTH + 1];
  1392. DWORD offset;
  1393. for ( offset = 0; offset < SCREEN_WIDTH; offset++) {
  1394. string[ offset] = SINGLE_HORIZONTAL;
  1395. }
  1396. string[ SCREEN_WIDTH] = NULLC;
  1397. GenOutputArg(TEXT("%ws\n"), string);
  1398. }
  1399. VOID
  1400. PrintTime(
  1401. DWORD_PTR JobTime
  1402. )
  1403. /*++
  1404. Routine Description:
  1405. Prints time of a job in HH:MM{A,P}M format.
  1406. Arguments:
  1407. JobTime - time in miliseconds (measured from midnight)
  1408. Return Value:
  1409. None.
  1410. Note:
  1411. BUGBUG this does not make sure that JobTime is within the bounds.
  1412. BUGBUG Also, there is nothing unicode about printing this output.
  1413. --*/
  1414. {
  1415. WCHAR Buffer[15];
  1416. GetTimeString( JobTime, Buffer, sizeof( Buffer)/sizeof( WCHAR) );
  1417. GenOutputArg( DUMP_FMT3, Buffer );
  1418. }
  1419. BOOL
  1420. TraverseSearchList(
  1421. IN PWCHAR String,
  1422. IN PSEARCH_LIST SearchList,
  1423. OUT PDWORD pValue
  1424. )
  1425. /*++
  1426. Routine Description:
  1427. Examines search list until it find the correct entry, then returns
  1428. the value corresponding to this entry.
  1429. Arguments:
  1430. String - string to match
  1431. SearchList - array of entries containing valid strings
  1432. pValue - value corresponding to a matching valid string
  1433. Return Value:
  1434. TRUE a matching entry was found
  1435. FALSE otherwise
  1436. --*/
  1437. {
  1438. if ( SearchList != NULL) {
  1439. for ( NOTHING; SearchList->String != NULL; SearchList++) {
  1440. if ( _wcsicmp( String, SearchList->String) == 0) {
  1441. *pValue = SearchList->Value;
  1442. return( TRUE) ;
  1443. }
  1444. }
  1445. }
  1446. return( FALSE) ;
  1447. }
  1448. VOID
  1449. Usage(
  1450. BOOL GoodCommand
  1451. )
  1452. /*++
  1453. Routine Description:
  1454. Usage of AT command.
  1455. Arguments:
  1456. GoodCommand - TRUE if we have a good command input (request for help)
  1457. FALSE if we have a bad command input
  1458. Return Value:
  1459. None.
  1460. --*/
  1461. {
  1462. if ( GlobalErrorReported == TRUE) {
  1463. PutNewLine();
  1464. } else if ( GoodCommand == FALSE) {
  1465. MessagePrint( IDS_INVALID_COMMAND );
  1466. }
  1467. MessagePrint( IDS_USAGE );
  1468. }
  1469. #define REG_SCHEDULE_PARMS TEXT("System\\CurrentControlSet\\Services\\Schedule\\Parameters")
  1470. #define REG_SCHEDULE_USE_OLD TEXT("UseOldParsing")
  1471. BOOL
  1472. UseOldParsing()
  1473. /*++
  1474. Routine Description:
  1475. Checks the registry for
  1476. HKLM\CurrentControlSet\Services\Schedule\parameters\UseOldParsing
  1477. If present and equal to 1, then revert to 3.51 level of command line
  1478. parsing. Spaces in filenames will not work with this option. This is
  1479. intended as a migration path for customers who cannot change all their
  1480. command scripts that use AT.EXE right away.
  1481. --*/
  1482. {
  1483. BOOL fUseOld = FALSE;
  1484. LONG err = 0;
  1485. do { // Error breakout loop
  1486. HKEY hkeyScheduleParms;
  1487. DWORD dwType;
  1488. DWORD dwData = 0;
  1489. DWORD cbData = sizeof(dwData);
  1490. // Break out on any error and use the default, FALSE.
  1491. if (err = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  1492. REG_SCHEDULE_PARMS,
  1493. 0,
  1494. KEY_READ,
  1495. &hkeyScheduleParms))
  1496. {
  1497. break;
  1498. }
  1499. if (err = RegQueryValueEx(hkeyScheduleParms,
  1500. REG_SCHEDULE_USE_OLD,
  1501. NULL,
  1502. &dwType,
  1503. (LPBYTE)&dwData,
  1504. &cbData ))
  1505. {
  1506. RegCloseKey( hkeyScheduleParms );
  1507. break;
  1508. }
  1509. if ( dwType == REG_DWORD && dwData == 1 )
  1510. {
  1511. fUseOld = TRUE;
  1512. }
  1513. RegCloseKey( hkeyScheduleParms );
  1514. } while (FALSE) ;
  1515. return fUseOld;
  1516. }
  1517. BOOL
  1518. ValidateCommand(
  1519. IN int argc,
  1520. IN WCHAR ** argv,
  1521. OUT int * pCommand
  1522. )
  1523. /*++
  1524. Routine Description:
  1525. Examines command line to see what to do. This validates the command
  1526. line passed into the AT command processor. If this routine finds any
  1527. invalid data, the program exits with an appropriate error message.
  1528. Arguments:
  1529. pCommand - pointer to command
  1530. argc - count of arguments
  1531. argv - pointer to table of arguments
  1532. Return Value:
  1533. FALSE - if failure, i.e. command will not be executed
  1534. TRUE - if success
  1535. Comment:
  1536. Parsing assumes:
  1537. non-switch (positional) parameters come first and order among these
  1538. parameters is important
  1539. switch parameters come second and order among these parameters is
  1540. NOT important
  1541. command (if present) comes last
  1542. --*/
  1543. {
  1544. int i; // loop index
  1545. int next; // index of Time or JobId argument
  1546. int argno; // where to start in arg string
  1547. BOOL DeleteFound; // did we find a delete switch
  1548. WCHAR * recdatap; // ptr used to build atr_command
  1549. DWORD recdata_len; // len of arg to put in atr_command
  1550. DWORD_PTR JobTime;
  1551. BOOL fUseOldParsing = FALSE;
  1552. if (argc == 1) {
  1553. *pCommand = DUMP_ALL;
  1554. return( TRUE);
  1555. }
  1556. // First look for a help switch on the command line.
  1557. for ( i = 1; i < argc; i++ ) {
  1558. if ( !_wcsicmp( argv[i], QUESTION_SW)
  1559. || !_wcsicmp( argv[i], QUESTION_SW_TOO)) {
  1560. *pCommand = ACTION_USAGE;
  1561. return( TRUE);
  1562. }
  1563. }
  1564. next = ( ArgIsServerName( argv[ 1]) == TRUE) ? 2 : 1;
  1565. if ( argc == next) {
  1566. *pCommand = DUMP_ALL;
  1567. return( TRUE);
  1568. }
  1569. if ( (ArgIsDecimalString( argv[ next], &GlobalJobId)) == TRUE) {
  1570. if ( argc == next + 1) {
  1571. *pCommand = DUMP_ID;
  1572. return( TRUE);
  1573. }
  1574. if ( ParseJobIdArgs( argv, argc, next + 1, &DeleteFound) == FALSE) {
  1575. return( FALSE); // an invalid argument
  1576. }
  1577. *pCommand = (DeleteFound == FALSE) ? DUMP_ID : DEL_ID;
  1578. return( TRUE);
  1579. }
  1580. //
  1581. // Try some variation of "AT [\\ServerName [/DELETE]"
  1582. //
  1583. if ( ParseJobIdArgs( argv, argc, next, &DeleteFound) == TRUE) {
  1584. *pCommand = (DeleteFound == FALSE) ? DUMP_ALL : DEL_ALL;
  1585. return( TRUE);
  1586. }
  1587. if ( ArgIsTime( argv[ next], &JobTime) == TRUE) {
  1588. *pCommand = ADD_TO_SCHEDULE;
  1589. if ( argc < next + 2) {
  1590. return( FALSE); // need something to do, not just time
  1591. }
  1592. memset( (PBYTE)&GlobalAtInfo, '\0', sizeof(GlobalAtInfo)); // initialize
  1593. GlobalAtInfo.Flags |= JOB_NONINTERACTIVE; // the default
  1594. if ( ParseInteractiveArg( argv[ next + 1])) {
  1595. next++;
  1596. }
  1597. if ( argc < next + 2) {
  1598. return( FALSE); // once more with feeling
  1599. }
  1600. if ( ParseTimeArgs( argv, argc, next + 1, &argno) == FALSE) {
  1601. return( FALSE);
  1602. }
  1603. // Copy argument strings to record.
  1604. recdatap = GlobalAtInfo.Command = GlobalAtInfoCommand;
  1605. recdata_len = 0;
  1606. fUseOldParsing = UseOldParsing();
  1607. for ( i = argno; i < argc; i++) {
  1608. DWORD temp;
  1609. //
  1610. // Fix for bug 22068 "AT command does not handle filenames with
  1611. // spaces." The command processor takes a quoted command line arg
  1612. // and puts everything between the quotes into one argv string.
  1613. // The quotes are stripped out. Thus, if any of the string args
  1614. // contain whitespace, then they must be requoted before being
  1615. // concatenated into the command value.
  1616. //
  1617. BOOL fQuote = (!fUseOldParsing && wcschr(argv[i], L' ') != NULL);
  1618. temp = wcslen(argv[i]) + (fQuote ? 3 : 1); // add 2 for quotes
  1619. recdata_len += temp;
  1620. if ( recdata_len > MAX_COMMAND_LEN) {
  1621. MessagePrint( APE_AT_COMMAND_TOO_LONG );
  1622. return( FALSE);
  1623. }
  1624. if (fQuote)
  1625. {
  1626. wcscpy(recdatap, L"\"");
  1627. wcscat(recdatap, argv[i]);
  1628. wcscat(recdatap, L"\"");
  1629. }
  1630. else
  1631. {
  1632. wcscpy(recdatap, argv[i]);
  1633. }
  1634. recdatap += temp;
  1635. // To construct lpszCommandLine argument to CreateProcess call
  1636. // we replace nuls with spaces.
  1637. *(recdatap - 1) = BLANK;
  1638. }
  1639. // Reset space back to null on last argument in string.
  1640. *(recdatap - 1) = NULLC;
  1641. GlobalAtInfo.JobTime = JobTime;
  1642. return( TRUE);
  1643. }
  1644. return( FALSE);
  1645. }
  1646. VOID
  1647. GetTimeString(
  1648. DWORD_PTR Time,
  1649. WCHAR *Buffer,
  1650. int BufferLength
  1651. )
  1652. /*++
  1653. Routine Description:
  1654. This function converts a dword time to an ASCII string.
  1655. Arguments:
  1656. Time - Time difference in dword from start of the day (i.e. 12am
  1657. midnight ) in milliseconds
  1658. Buffer - Pointer to the buffer to place the ASCII representation.
  1659. BufferLength - The length of buffer in bytes.
  1660. Return Value:
  1661. None.
  1662. --*/
  1663. #define MINUTES_IN_HOUR 60
  1664. #define SECONDS_IN_MINUTE 60
  1665. {
  1666. WCHAR szTimeString[MAX_TIME_SIZE];
  1667. WCHAR *p = &szTimeString[1];
  1668. DWORD_PTR seconds, minutes, hours;
  1669. int numChars;
  1670. DWORD flags;
  1671. SYSTEMTIME st;
  1672. GetSystemTime(&st);
  1673. *p = NULLC;
  1674. // Check if the time format is initialized. If not, initialize it.
  1675. if ( GlobalTimeFormat.AMString == NULL )
  1676. NetpGetTimeFormat( &GlobalTimeFormat );
  1677. // Convert the time to hours, minutes, seconds
  1678. seconds = (Time/1000);
  1679. hours = seconds / (MINUTES_IN_HOUR * SECONDS_IN_MINUTE );
  1680. seconds -= hours * MINUTES_IN_HOUR * SECONDS_IN_MINUTE;
  1681. minutes = seconds / SECONDS_IN_MINUTE;
  1682. seconds -= minutes * SECONDS_IN_MINUTE;
  1683. st.wHour = (WORD)(hours);
  1684. st.wMinute = (WORD)(minutes);
  1685. st.wSecond = (WORD)(seconds);
  1686. st.wMilliseconds = 0;
  1687. flags = TIME_NOSECONDS;
  1688. if (!GlobalTimeFormat.TwelveHour)
  1689. flags |= TIME_FORCE24HOURFORMAT;
  1690. numChars = GetTimeFormatW(GetThreadLocale(),
  1691. flags, &st, NULL, p, MAX_TIME_SIZE-1);
  1692. if ( numChars > BufferLength )
  1693. numChars = BufferLength;
  1694. if (*(p+1) == ARG_SEP_CHR && GlobalTimeFormat.LeadingZero) {
  1695. *(--p) = TEXT('0');
  1696. numChars++;
  1697. }
  1698. wcsncpy( Buffer, p, numChars );
  1699. // Append spece for align print format. column based.
  1700. {
  1701. DWORD ColumnLength;
  1702. // character counts -> array index.
  1703. numChars--;
  1704. ColumnLength = GetStringColumn( Buffer );
  1705. while( ColumnLength++ < MAX_TIME_FIELD_LENGTH) {
  1706. Buffer[ numChars++] = BLANK;
  1707. }
  1708. Buffer[ numChars] = UNICODE_NULL;
  1709. }
  1710. }
  1711. BOOL
  1712. InitList( PSEARCH_LIST SearchList )
  1713. {
  1714. if ( SearchList != NULL) {
  1715. if ( SearchList->String != NULL ) // Already initialized
  1716. return TRUE;
  1717. for ( NOTHING; SearchList->MessageId != 0; SearchList++) {
  1718. if ( MessageGet( SearchList->MessageId,
  1719. &SearchList->String,
  1720. 0 ) == 0 )
  1721. {
  1722. return FALSE;
  1723. }
  1724. }
  1725. }
  1726. return TRUE;
  1727. }
  1728. VOID
  1729. TermList( PSEARCH_LIST SearchList )
  1730. {
  1731. if ( SearchList != NULL) {
  1732. if ( SearchList->String == NULL ) // Not initialized
  1733. return;
  1734. for ( NOTHING; SearchList->String != NULL; SearchList++) {
  1735. LocalFree( SearchList->String );
  1736. }
  1737. }
  1738. }
  1739. DWORD
  1740. GetStringColumn( WCHAR *lpwstr )
  1741. {
  1742. int cchNeed;
  1743. cchNeed = WideCharToMultiByte( GetConsoleOutputCP() , 0 ,
  1744. lpwstr , -1 ,
  1745. NULL , 0 ,
  1746. NULL , NULL );
  1747. return( (DWORD) cchNeed - 1 ); // - 1 : remove NULL
  1748. }